diff --git a/CHANGELOG.md b/CHANGELOG.md index 48fb0f35c..4eb2c8a76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Added ### Fixed +* Fix basic case of false positive of duplicate label inspection when user defined \if commands are used +* Fix a parse error when using \else with a user defined \if-command ## [0.9.9-alpha.7] - 2024-11-24 @@ -23,7 +25,6 @@ * Add setting to disable auto-import of bibtex entries from remote libraries ### Fixed - * Fix 'missing import' false positive in subfiles * Don't override the file icon for .txt files, by @Steve-Li-1998 * Fix exceptions #3754 and #3326 diff --git a/src/nl/hannahsten/texifyidea/grammar/Latex.bnf b/src/nl/hannahsten/texifyidea/grammar/Latex.bnf index 6abce36dc..169aee34d 100644 --- a/src/nl/hannahsten/texifyidea/grammar/Latex.bnf +++ b/src/nl/hannahsten/texifyidea/grammar/Latex.bnf @@ -57,7 +57,7 @@ latexFile ::= content content ::= no_math_content* // When updating this list, consider updating other _content lists -no_math_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | if_block | math_environment | COMMAND_IFNEXTCHAR | commands | group | normal_text | END_IF +no_math_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | if_block | math_environment | COMMAND_IFNEXTCHAR | commands | group | normal_text | END_IF | ELSE normal_text ::= (NORMAL_TEXT_WORD | STAR | AMPERSAND | QUOTATION_MARK | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET | OPEN_PAREN | CLOSE_PAREN | OPEN_BRACKET | CLOSE_BRACKET | PIPE | EXCLAMATION_MARK | BACKSLASH | EQUALS | COMMA | ANGLE_PARAM)+ @@ -76,7 +76,7 @@ pseudocode_block ::= BEGIN_PSEUDOCODE_BLOCK parameter* pseudocode_block_content? pseudocode_block_content ::= no_math_content* -// Plain TeX \if...\fi, note that user defined ifs are not included so there may be unmatched \fi +// Plain TeX \if...\fi, note that user defined ifs are not included so there may be unmatched \fi or \else if_block ::= START_IF if_block_content? (ELSE if_block_content?)* END_IF { pin=1 } // no_math_content without end_if @@ -125,10 +125,10 @@ picture_param ::= OPEN_PAREN picture_param_content* CLOSE_PAREN { pin=3 } // These are like content, but no brackets and with parameter_text instead of normal_text // We have to separate optional and required parameter content, because required parameter content // can contain mismatched brackets, but optional parameters not (then we wouldn't know what to match) -optional_param_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | if_block | math_environment | COMMAND_IFNEXTCHAR | commands | group | OPEN_PAREN | CLOSE_PAREN | parameter_text | BACKSLASH | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET | END_IF -required_param_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | if_block | math_environment | COMMAND_IFNEXTCHAR | group | OPEN_PAREN | CLOSE_PAREN | parameter_text | COMMA | EQUALS | OPEN_BRACKET | CLOSE_BRACKET | BACKSLASH | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET | END_IF | ANGLE_PARAM +optional_param_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | if_block | math_environment | COMMAND_IFNEXTCHAR | commands | group | OPEN_PAREN | CLOSE_PAREN | parameter_text | BACKSLASH | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET | END_IF | ELSE +required_param_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | if_block | math_environment | COMMAND_IFNEXTCHAR | group | OPEN_PAREN | CLOSE_PAREN | parameter_text | COMMA | EQUALS | OPEN_BRACKET | CLOSE_BRACKET | BACKSLASH | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET | END_IF | ELSE | ANGLE_PARAM // Cannot contain ( or ) -picture_param_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | if_block | math_environment | COMMAND_IFNEXTCHAR | commands | group | parameter_text | BACKSLASH | COMMA | EQUALS | OPEN_BRACKET | CLOSE_BRACKET | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET | END_IF +picture_param_content ::= raw_text | magic_comment | comment | environment | pseudocode_block | if_block | math_environment | COMMAND_IFNEXTCHAR | commands | group | parameter_text | BACKSLASH | COMMA | EQUALS | OPEN_BRACKET | CLOSE_BRACKET | OPEN_ANGLE_BRACKET | CLOSE_ANGLE_BRACKET | END_IF | ELSE strict_key_val_pair ::= key_val_key EQUALS key_val_value? diff --git a/src/nl/hannahsten/texifyidea/inspections/latex/redundancy/LatexDuplicateLabelInspection.kt b/src/nl/hannahsten/texifyidea/inspections/latex/redundancy/LatexDuplicateLabelInspection.kt index 636993cd4..a93c42d9e 100644 --- a/src/nl/hannahsten/texifyidea/inspections/latex/redundancy/LatexDuplicateLabelInspection.kt +++ b/src/nl/hannahsten/texifyidea/inspections/latex/redundancy/LatexDuplicateLabelInspection.kt @@ -6,22 +6,18 @@ import com.intellij.codeInspection.ProblemHighlightType import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile +import com.intellij.psi.util.elementType import com.intellij.psi.util.startOffset import nl.hannahsten.texifyidea.inspections.InsightGroup import nl.hannahsten.texifyidea.inspections.TexifyInspectionBase import nl.hannahsten.texifyidea.lang.alias.CommandManager import nl.hannahsten.texifyidea.lang.magic.MagicCommentScope -import nl.hannahsten.texifyidea.psi.LatexCommands -import nl.hannahsten.texifyidea.psi.LatexEnvironment -import nl.hannahsten.texifyidea.psi.LatexParameter -import nl.hannahsten.texifyidea.psi.getEnvironmentName +import nl.hannahsten.texifyidea.psi.* import nl.hannahsten.texifyidea.util.labels.findBibitemCommands import nl.hannahsten.texifyidea.util.labels.findLatexLabelingElementsInFileSet import nl.hannahsten.texifyidea.util.magic.CommandMagic import nl.hannahsten.texifyidea.util.magic.EnvironmentMagic -import nl.hannahsten.texifyidea.util.parser.isDefinitionOrRedefinition -import nl.hannahsten.texifyidea.util.parser.parentOfType -import nl.hannahsten.texifyidea.util.parser.requiredParameter +import nl.hannahsten.texifyidea.util.parser.* import java.lang.Integer.max import java.util.* @@ -128,6 +124,10 @@ open class LatexDuplicateLabelInspection : TexifyInspectionBase() { .mapNotNull { command -> // When the label is defined in a command definition ignore it, because there could be more than one with #1 as parameter if (command.parentOfType(LatexCommands::class).isDefinitionOrRedefinition()) return@mapNotNull null + // If the command is within an \if branch, ignore it because it will may appear in multiple branches of which only one will be present during compilation + if (isPreviousConditionalStart(command) && isNextConditionalEnd(command)) { + return@mapNotNull null + } command.getLabelDescriptor() } .groupBy { it.label } @@ -151,4 +151,54 @@ open class LatexDuplicateLabelInspection : TexifyInspectionBase() { isOntheFly ) } + + /** + * If the next relevant command is a \fi + */ + private fun isNextConditionalEnd(current: PsiElement): Boolean { + return isEndConditional(nextConditionalCommand(current, searchBackwards = false) ?: return false) + } + + /** + * If the previous relevant command is an \if + */ + private fun isPreviousConditionalStart(current: PsiElement): Boolean { + return isStartConditional(nextConditionalCommand(current, searchBackwards = true) ?: return false) + } + + /** + * Next relevant command. There are many ways in which this does not work, but since this is just an inspection this is much safer than trying to parse user defined \if commands in the parser, which is impossiblee + */ + private fun nextConditionalCommand(element: PsiElement, searchBackwards: Boolean): PsiElement? { + var current = element.parentOfType(LatexNoMathContent::class) + while (current != null && !isConditional(current)) { + current = if (!searchBackwards) { + current.nextSibling?.nextSiblingOfType(LatexNoMathContent::class) + } + else { + current.prevSibling?.previousSiblingOfType(LatexNoMathContent::class) + } + } + return current + } + + private fun isConditional(element: PsiElement): Boolean { + return isStartConditional(element) || isEndConditional(element) + } + + private fun isStartConditional(rootElement: PsiElement): Boolean { + // To keep it simple, only look one level down + for (element in rootElement.children + listOf(rootElement)) { + if (element is LatexCommands && element.name?.startsWith("\\if") == true) return true + if (element.elementType == LatexTypes.START_IF) return true + } + return false + } + + private fun isEndConditional(rootElement: PsiElement): Boolean { + for (element in rootElement.children + listOf(rootElement)) { + if (element.firstChild?.elementType in setOf(LatexTypes.ELSE, LatexTypes.END_IF)) return true + } + return false + } } diff --git a/test/nl/hannahsten/texifyidea/inspections/latex/redundancy/LatexDuplicateLabelInspectionTest.kt b/test/nl/hannahsten/texifyidea/inspections/latex/redundancy/LatexDuplicateLabelInspectionTest.kt index 8a155ea98..5da8366b7 100644 --- a/test/nl/hannahsten/texifyidea/inspections/latex/redundancy/LatexDuplicateLabelInspectionTest.kt +++ b/test/nl/hannahsten/texifyidea/inspections/latex/redundancy/LatexDuplicateLabelInspectionTest.kt @@ -98,4 +98,18 @@ class LatexDuplicateLabelInspectionTest : TexifyInspectionTestBase(LatexDuplicat ) myFixture.checkHighlighting() } + + fun testIfs() { + myFixture.configureByText( + LatexFileType, + """ + \ifdog + \section{DOG}\label{sec:pet} + \else + \section{CAT}\label{sec:pet} + \fi + """.trimIndent() + ) + myFixture.checkHighlighting() + } } \ No newline at end of file diff --git a/test/nl/hannahsten/texifyidea/psi/LatexParserTest.kt b/test/nl/hannahsten/texifyidea/psi/LatexParserTest.kt index e1f300e97..f0bfc5050 100644 --- a/test/nl/hannahsten/texifyidea/psi/LatexParserTest.kt +++ b/test/nl/hannahsten/texifyidea/psi/LatexParserTest.kt @@ -189,6 +189,7 @@ class LatexParserTest : BasePlatformTestCase() { LatexFileType, """ I write \State \Until I \Repeat \EndProcedure. + \ifdog DOG \else CAT \fi """.trimIndent() ) myFixture.checkHighlighting()