Skip to content

Commit

Permalink
Merge pull request #3081 from Hannah-Sten/inspection/suspicious-sec-f…
Browse files Browse the repository at this point in the history
…ormat

Add inspection for suspicious formatting in section-like commands
  • Loading branch information
PHPirates authored May 29, 2023
2 parents 02a592e + 418b548 commit 33edead
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
groupPath="LaTeX" groupName="Probable bugs" displayName="Unresolved references"
enabledByDefault="true"
level="WARNING" />
<localInspection language="Latex" implementationClass="nl.hannahsten.texifyidea.inspections.latex.probablebugs.LatexSuspiciousSectionFormattingInspection"
groupPath="LaTeX" groupName="Probable bugs" displayName="Suspicious formatting in section-like command"
enabledByDefault="true"
level="WARNING" />
<localInspection language="Latex" implementationClass="nl.hannahsten.texifyidea.inspections.latex.probablebugs.LatexNonMatchingEnvironmentInspection"
groupPath="LaTeX" groupName="Probable bugs" displayName="Non matching environment commands"
enabledByDefault="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html>
<body>
Reports usages of <tt>~</tt> and <tt>\\</tt> in a Section-like command and suggests to add an optional argument to the
command.
<p>
This inspection is based on the following paragraph from The LaTeX Companion (2nd edition, page 23):
<p>
If you try to advise TeX on how to split the heading over a few lines using the '~' symbol so the '\\' command, then
side effects may result when formatting the table of contents or generating the running head.
In this case the simplest solution is to repeat the heading text without the specific markup in the optional
parameter of the sectioning command.
<!-- tooltip end -->
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package nl.hannahsten.texifyidea.inspections.latex.probablebugs

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.openapi.util.TextRange
import com.intellij.psi.PsiFile
import com.intellij.webSymbols.references.WebSymbolReferenceProvider.Companion.startOffsetIn
import nl.hannahsten.texifyidea.inspections.InsightGroup
import nl.hannahsten.texifyidea.inspections.TexifyInspectionBase
import nl.hannahsten.texifyidea.psi.LatexCommands
import nl.hannahsten.texifyidea.psi.LatexPsiHelper
import nl.hannahsten.texifyidea.psi.LatexRequiredParam
import nl.hannahsten.texifyidea.util.containsAny
import nl.hannahsten.texifyidea.util.files.commandsInFile
import nl.hannahsten.texifyidea.util.firstChildOfType
import nl.hannahsten.texifyidea.util.magic.CommandMagic
import nl.hannahsten.texifyidea.util.requiredParameter

open class LatexSuspiciousSectionFormattingInspection : TexifyInspectionBase() {

override val inspectionGroup = InsightGroup.LATEX

override fun getDisplayName() = "Suspicious formatting in the required argument of a sectioning command"

override val inspectionId = "SuspiciousSectionFormatting"

override fun inspectFile(file: PsiFile, manager: InspectionManager, isOntheFly: Boolean): List<ProblemDescriptor> {
return file.commandsInFile()
.asSequence()
.filter { it.name in CommandMagic.sectionMarkers }
.filter { it.optionalParameterMap.isEmpty() }
.filter { it.requiredParameter(0)?.containsAny(formatting) == true }
.map { psiElement ->
val requiredParam = psiElement.firstChildOfType(LatexRequiredParam::class)
// Plus 1 for the opening brace.
val startOffset = requiredParam?.startOffsetIn(psiElement)?.plus(1) ?: 0
// Minus 2 for the braces surrounding the parameter.
val endOffset = requiredParam?.textLength?.minus(2)?.plus(startOffset) ?: psiElement.textLength
manager.createProblemDescriptor(
psiElement,
TextRange(startOffset, endOffset),
"Suspicious formatting in ${psiElement.name}",
ProblemHighlightType.WARNING,
isOntheFly,
AddOptionalArgumentQuickFix()
)
}
.toList()
}

class AddOptionalArgumentQuickFix : LocalQuickFix {

override fun getFamilyName(): String {
return "Fix formatting in table of contents and running head"
}

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val command = descriptor.psiElement as LatexCommands
val requiredParamText = command.requiredParameter(0)
val optionalParamText = requiredParamText?.replace(Regex(formatting.joinToString("", prefix = "[", postfix = "]")), " ")
?: return
val optionalArgument = LatexPsiHelper(project).createOptionalParameter(optionalParamText)

command.addAfter(optionalArgument, command.commandToken)
// Create a new command and completely replace the old command so all the psi methods will recompute instead
// of using old values from their cache.
val newCommand = LatexPsiHelper(project).createFromText(command.text).firstChildOfType(LatexCommands::class)
?: return
command.replace(newCommand)
}
}

companion object {

val formatting = setOf("~", "\\\\")
}
}
5 changes: 5 additions & 0 deletions src/nl/hannahsten/texifyidea/psi/LatexPsiHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ class LatexPsiHelper(private val project: Project) {
return createFromText(commandText).firstChildOfType(LatexRequiredParam::class)!!
}

fun createOptionalParameter(content: String): LatexOptionalParam {
val commandText = "\\section[$content]{$content}"
return createFromText(commandText).firstChildOfType(LatexOptionalParam::class)!!
}

/**
* Returns the LatexOptionalParam node that is supposed to contain the label key for the command.
* If no such node exists yet, a new one is created at the correct position.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package nl.hannahsten.texifyidea.inspections.latex.probablebugs

import nl.hannahsten.texifyidea.file.LatexFileType
import nl.hannahsten.texifyidea.inspections.TexifyInspectionTestBase

internal class LatexSuspiciousSectionFormattingInspectionTest : TexifyInspectionTestBase(LatexSuspiciousSectionFormattingInspection()) {

fun `test ~ warning`() {
myFixture.configureByText(
LatexFileType,
"\\section{<warning descr=\"Suspicious formatting in \\section\">You should not use~in the title of a section</warning>}"
)
myFixture.checkHighlighting(true, false, true, false)
}

fun `test ~ warning for short section`() {
myFixture.configureByText(
LatexFileType,
"\\section{<warning descr=\"Suspicious formatting in \\section\">a~b</warning>}"
)
myFixture.checkHighlighting(true, false, true, false)
}

fun `test backslash warning`() {
myFixture.configureByText(
LatexFileType,
"\\section{<warning descr=\"Suspicious formatting in \\section\">You should not use\\\\in the title of a section</warning>}"
)
myFixture.checkHighlighting(true, false, true, false)
}

fun `test multiple warnings in one section`() {
myFixture.configureByText(
LatexFileType,
"\\section{<warning descr=\"Suspicious formatting in \\section\">You should not use~in the title~of a section</warning>}"
)
myFixture.checkHighlighting(true, false, true, false)
}

fun `test no warning for ~ when optional argument is present`() {
myFixture.configureByText(
LatexFileType,
"\\section[Table of contents long title]{Title with explicit~formatting}"
)
myFixture.checkHighlighting(true, false, true, false)
}

fun `test no warning for backslash when optional argument is present`() {
myFixture.configureByText(
LatexFileType,
"\\section[Table of contents long title]{Title with explicit \\\\ formatting}"
)
myFixture.checkHighlighting(true, false, true, false)
}

fun `test simple quickfix for ~`() {
myFixture.configureByText(
LatexFileType,
"\\section{You should not use~in the title}"
)
testQuickFix("\\section{You should not use~in the title}", "\\section[You should not use in the title]{You should not use~in the title}")
myFixture.checkHighlighting(true, false, true, false)
}

fun `test simple quickfix for backslash`() {
myFixture.configureByText(
LatexFileType,
"\\section{You should not use~in the title}"
)
testQuickFix("\\section{You should not use \\\\ in the title}", "\\section[You should not use in the title]{You should not use \\\\ in the title}")
myFixture.checkHighlighting(true, false, true, false)
}
}

0 comments on commit 33edead

Please sign in to comment.