Skip to content

Commit

Permalink
Merge pull request #3760 from Hannah-Sten/if-else-label
Browse files Browse the repository at this point in the history
Fix basic case of false positive of duplicate label inspection when user defined \if commands are used
  • Loading branch information
PHPirates authored Nov 26, 2024
2 parents 787188d + 0b8e005 commit 6512df5
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 13 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions src/nl/hannahsten/texifyidea/grammar/Latex.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -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)+

Expand All @@ -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
Expand Down Expand Up @@ -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?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*

Expand Down Expand Up @@ -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 }
Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
1 change: 1 addition & 0 deletions test/nl/hannahsten/texifyidea/psi/LatexParserTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ class LatexParserTest : BasePlatformTestCase() {
LatexFileType,
"""
I write \State \Until I \Repeat \EndProcedure.
\ifdog DOG \else CAT \fi
""".trimIndent()
)
myFixture.checkHighlighting()
Expand Down

0 comments on commit 6512df5

Please sign in to comment.