Skip to content
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,22 @@

unitFile ::= section_items* section_groups*

section_groups ::= section_ section_items* { name="section header" pin=1 }
section_groups ::= section_ section_items* { name="section header" pin=1 recoverWhile=section_recover }

private section_items ::= property | comment_

property ::= key_ separator_ value? { pin=1 }
property ::= key_ separator_ value? { pin=1 recoverWhile=property_recover }

value ::= (CONTINUING_VALUE| comment_ )* COMPLETED_VALUE

// Error recovery (#562). A line that is not a section, comment or key=value (e.g. "some free text") lexes as a KEY
// followed by BAD_CHARACTERs. Without recovery the parser gave up there and the rest of the file became one error
// element, so nothing after it was validated. systemd itself logs "Missing '='" and carries on with the next line.
// After a (possibly broken) property, skip to the next token that can start a line item; if junk appears where an
// item should start, skip to the next section header.
private property_recover ::= !(KEY | SECTION | COMMENT)
private section_recover ::= !SECTION

// Single rules for tokens so that I can alias them for better error handling shown above.
private comment_ ::= COMMENT {name="comment" }
private section_ ::= SECTION
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package net.sjrx.intellij.plugins.systemdunitfiles.inspections

import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest

/**
* A line that is not a section, comment or key=value must not stop validation of the lines after it (#562).
* systemd logs "Missing '=', ignoring line." and keeps going; the plugin used to stop validating the rest of the file.
*/
class InvalidValueAfterLineWithoutSeparatorTest : AbstractUnitFileTest() {

private fun invalidValueHighlightTexts(fileName: String, file: String): List<String> {
setupFileInEditor(fileName, file)
enableInspection(InvalidValueInspection::class.java)
return myFixture.doHighlighting().filter { it.inspectionToolId == "InvalidValue" }.map { it.text }
}

fun testValuesAfterAFreeTextLineInTheSameSectionAreStillValidated() {
// language="unit file (systemd)"
val file = """
[Service]
Nice=abc
this line has no equals
Nice=def
""".trimIndent()

assertEquals(listOf("abc", "def"), invalidValueHighlightTexts("file.service", file))
}

fun testLaterSectionsAfterAFreeTextLineAreStillValidated() {
// The shape that hid ~300 invalid values in the kf6-syntax-highlighting test unit: free text inside an
// extension section, followed by more sections.
// language="unit file (systemd)"
val file = """
[X-Unit]
name = value
Some text in an extension section.
The extension section ends with the next section header.

[Service]
Nice=abc
[Unit]
StartLimitIntervalSec=invalid
""".trimIndent()

assertEquals(listOf("abc", "invalid"), invalidValueHighlightTexts("file.service", file))
}

fun testFreeTextBeforeTheFirstSectionDoesNotStopValidation() {
// language="unit file (systemd)"
val file = """
some free text here
[Service]
Nice=abc
""".trimIndent()

assertEquals(listOf("abc"), invalidValueHighlightTexts("file.service", file))
}

fun testValidFileIsUnaffected() {
// language="unit file (systemd)"
val file = """
[Service]
Nice=5
[Unit]
StartLimitIntervalSec=10
""".trimIndent()

assertEquals(emptyList<String>(), invalidValueHighlightTexts("file.service", file))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package net.sjrx.intellij.plugins.systemdunitfiles.parser

import com.intellij.testFramework.ParsingTestCase
import junit.framework.TestCase
import com.intellij.psi.PsiErrorElement
import com.intellij.psi.util.PsiTreeUtil
import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFileProperty
import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFileSectionGroups
import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFileValueType

Expand Down Expand Up @@ -769,6 +772,37 @@ KeyThree=value 2\
*/assertSameLines(expectedPsiTree, parseTree)
}

fun testLineWithoutSeparatorDoesNotSwallowTheRestOfTheFile() {
// A free-text line (several words, no '=') used to make the parser give up, turning everything after it into
// one error element (#562). It must stay a local error and the following properties and sections must parse.
val sourceCode = "[Service]\nNice=abc\nthis line has no equals\nNice=def\n[Unit]\nDescription=x\n"

val file = createPsiFile("a", sourceCode)
ensureParsed(file)
TestCase.assertEquals(sourceCode, file.text)

val sections = PsiTreeUtil.getChildrenOfTypeAsList(file, UnitFileSectionGroups::class.java)
TestCase.assertEquals(listOf("[Service]", "[Unit]"), sections.map { it.sectionName.let { n -> "[$n]" } })

val properties = PsiTreeUtil.findChildrenOfType(file, UnitFileProperty::class.java).map { it.key to it.valueText }
TestCase.assertEquals(listOf("Nice" to "abc", "this" to null, "Nice" to "def", "Description" to "x"), properties)

val errors = PsiTreeUtil.findChildrenOfType(file, PsiErrorElement::class.java)
TestCase.assertEquals(1, errors.size)
TestCase.assertTrue(errors.first().errorDescription.contains("key-value separator"))
}

fun testLineWithoutSeparatorBeforeFirstSectionDoesNotSwallowTheRestOfTheFile() {
val sourceCode = "some free text here\n[Service]\nNice=abc\n"

val file = createPsiFile("a", sourceCode)
ensureParsed(file)

val properties = PsiTreeUtil.findChildrenOfType(file, UnitFileProperty::class.java).map { it.key to it.valueText }
TestCase.assertEquals(listOf("some" to null, "Nice" to "abc"), properties)
TestCase.assertEquals(1, PsiTreeUtil.getChildrenOfTypeAsList(file, UnitFileSectionGroups::class.java).size)
}

/**
* Converts the source code supplied as an argument to a parse tree.
*
Expand Down
Loading