diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt index 1320d6a..7e11c8e 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt @@ -320,6 +320,28 @@ fun getAllAIGeneratedValidators(): Map { // legitimate value; ConditionCPUFeature= alone accounted for 110 false positives in a 6.7k-unit corpus. Validator("config_parse_unit_condition_string", "CONDITION_CONTROL_GROUP_CONTROLLER") to ConfigParseUnitConditionControlGroupControllerOptionValue() as OptionValueInformation, Validator("config_parse_unit_condition_string", "CONDITION_CPU_FEATURE") to ConfigParseUnitConditionCpuFeatureOptionValue() as OptionValueInformation, + // Structured [Unit] Condition*=/Assert*= parameters, each modelled on its condition_test_* runtime check. + Validator("config_parse_unit_condition_string", "CONDITION_CPUS") to ConfigParseUnitConditionCpusOptionValue() as OptionValueInformation, + Validator("config_parse_unit_condition_string", "CONDITION_MEMORY") to ConfigParseUnitConditionMemoryOptionValue() as OptionValueInformation, + Validator("config_parse_unit_condition_string", "CONDITION_CREDENTIAL") to ConfigParseUnitConditionCredentialOptionValue() as OptionValueInformation, + Validator("config_parse_unit_condition_string", "CONDITION_KERNEL_MODULE_LOADED") to ConfigParseUnitConditionKernelModuleLoadedOptionValue() as OptionValueInformation, + Validator("config_parse_unit_condition_string", "CONDITION_USER") to ConfigParseUnitConditionUserOptionValue() as OptionValueInformation, + Validator("config_parse_unit_condition_string", "CONDITION_GROUP") to ConfigParseUnitConditionGroupOptionValue() as OptionValueInformation, + Validator("config_parse_unit_condition_string", "CONDITION_FIRMWARE") to ConfigParseUnitConditionFirmwareOptionValue() as OptionValueInformation, + // Conditions whose parameter systemd stores verbatim (fnmatch globs, kernel-cmdline/env words, version + // globs): config parsing imposes no shape, so these accept any non-empty value by design. + Validator("config_parse_unit_condition_string", "CONDITION_HOST") to ConfigParseUnitConditionHostOptionValue() as OptionValueInformation, + Validator("config_parse_unit_condition_string", "CONDITION_MACHINE_TAG") to ConfigParseUnitConditionMachineTagOptionValue() as OptionValueInformation, + Validator("config_parse_unit_condition_string", "CONDITION_KERNEL_COMMAND_LINE") to ConfigParseUnitConditionKernelCommandLineOptionValue() as OptionValueInformation, + Validator("config_parse_unit_condition_string", "CONDITION_ENVIRONMENT") to ConfigParseUnitConditionEnvironmentOptionValue() as OptionValueInformation, + Validator("config_parse_unit_condition_string", "CONDITION_VERSION") to ConfigParseUnitConditionVersionOptionValue() as OptionValueInformation, + // Structured Condition*= parameters with their own leaf grammars (batch 2). + Validator("config_parse_unit_condition_string", "CONDITION_FRACTION") to ConfigParseUnitConditionFractionOptionValue() as OptionValueInformation, + Validator("config_parse_unit_condition_string", "CONDITION_OS_RELEASE") to ConfigParseUnitConditionOsReleaseOptionValue() as OptionValueInformation, + // The three pressure conditions all dispatch to condition_test_psi, so they share one grammar. + Validator("config_parse_unit_condition_string", "CONDITION_MEMORY_PRESSURE") to ConfigParseUnitConditionPressureOptionValue() as OptionValueInformation, + Validator("config_parse_unit_condition_string", "CONDITION_CPU_PRESSURE") to ConfigParseUnitConditionPressureOptionValue() as OptionValueInformation, + Validator("config_parse_unit_condition_string", "CONDITION_IO_PRESSURE") to ConfigParseUnitConditionPressureOptionValue() as OptionValueInformation, // Enumerations and address forms that had no validator (#509). Validator("config_parse_netdev_kind", "0") to ConfigParseNetdevKindOptionValue() as OptionValueInformation, diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionCpusOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionCpusOptionValue.kt new file mode 100644 index 0000000..8a4780c --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionCpusOptionValue.kt @@ -0,0 +1,28 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.comparisonPrefixed +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.unsignedNumber + +/* + * [Unit] ConditionCPUs= / AssertCPUs=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionCPUs= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_CPUS) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_cpus + */ + +/** + * Validator for `[Unit] ConditionCPUs=` / `AssertCPUs=`. + * + * C function: config_parse_unit_condition_string with ltype CONDITION_CPUS. condition_test_cpus + * (src/shared/condition.c) reads an optional comparison operator via parse_compare_operator(&p, 0) — + * defaulting to `>=` when none is given — and then the number of CPUs with safe_atou(). safe_atou() + * passes base 0 to strtoul(), so the count may be written decimal, `0x` hex or leading-zero octal; + * [unsignedNumber] models exactly that, bounded by the `unsigned` range. + */ +class ConfigParseUnitConditionCpusOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString(comparisonPrefixed(unsignedNumber(4_294_967_296L))) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionCredentialOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionCredentialOptionValue.kt new file mode 100644 index 0000000..4be02c3 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionCredentialOptionValue.kt @@ -0,0 +1,35 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString + +/* + * [Unit] ConditionCredential= / AssertCredential=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionCredential= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_CREDENTIAL) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_credential + * https://github.com/systemd/systemd/blob/048970650c/src/shared/creds-util.c credential_name_valid + */ + +/** + * Validator for `[Unit] ConditionCredential=` / `AssertCredential=`. + * + * C function: config_parse_unit_condition_string with ltype CONDITION_CREDENTIAL. condition_test_credential + * (src/shared/condition.c) rejects the parameter outright unless credential_name_valid() accepts it, so + * that is the whole shape rule. credential_name_valid() (src/shared/creds-util.c) is + * filename_is_valid() && fdname_is_valid(): + * + * - filename_is_valid(): non-empty, not "." or "..", no "/", at most NAME_MAX (255) bytes. + * - fdname_is_valid(): every byte printable ASCII (>= 0x20 and < 0x7F), no ":". + * + * Together: one to 255 printable-ASCII characters, none of them `/` or `:`, and not the reserved + * names `.` or `..`. Space (0x20) is a printable character, so it is allowed inside a name. + */ +class ConfigParseUnitConditionCredentialOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString( + RegexTerminal(".+", "(?!\\.\\.?\$)[\\x20-\\x7E&&[^/:]]{1,255}") + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionEnvironmentOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionEnvironmentOptionValue.kt new file mode 100644 index 0000000..24ca28c --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionEnvironmentOptionValue.kt @@ -0,0 +1,26 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ANY_CONDITION_ARGUMENT +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString + +/* + * [Unit] ConditionEnvironment= / AssertEnvironment=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionEnvironment= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_ENVIRONMENT) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_environment + */ + +/** + * Validator for `[Unit] ConditionEnvironment=` / `AssertEnvironment=`. + * + * C function: config_parse_unit_condition_string with ltype CONDITION_ENVIRONMENT. condition_test_environment + * (src/shared/condition.c) matches the parameter against the manager's environment block — either a bare + * variable name or a full `NAME=value`. The parameter is stored and compared verbatim with no validity + * check, so any non-empty string is legitimate — see [ANY_CONDITION_ARGUMENT]. + */ +class ConfigParseUnitConditionEnvironmentOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString(ANY_CONDITION_ARGUMENT) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionFirmwareOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionFirmwareOptionValue.kt new file mode 100644 index 0000000..fda96a4 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionFirmwareOptionValue.kt @@ -0,0 +1,53 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.LiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString + +/* + * [Unit] ConditionFirmware= / AssertFirmware=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionFirmware= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_FIRMWARE) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_firmware + */ + +/** + * Validator for `[Unit] ConditionFirmware=` / `AssertFirmware=`. + * + * C function: config_parse_unit_condition_string with ltype CONDITION_FIRMWARE. condition_test_firmware + * (src/shared/condition.c) recognises exactly four forms and reports "unsupported" for anything else: + * + * - `uefi` + * - `device-tree` + * - `device-tree-compatible(DTC)` — the parser takes everything up to the last `)`, which must end the value + * - `smbios-field(FIELD OP VALUE)` — likewise closed by the last `)` + * + * The two parenthesised forms are matched as `keyword(` + argument + `)`; the argument is modelled loosely + * (any non-empty run without `)`), which covers every real device-tree compatible string and SMBIOS field + * expression. The rare case of a `)` inside the argument (the parser's strrchr() would keep it) is not + * modelled, matching the plugin's preference for never flagging a value systemd accepts over covering an + * exotic spelling. + */ +class ConfigParseUnitConditionFirmwareOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString( + AlternativeCombinator( + SequenceCombinator( + LiteralChoiceTerminal("device-tree-compatible("), + RegexTerminal("[^)]+", "[^)]+"), + LiteralChoiceTerminal(")"), + ), + SequenceCombinator( + LiteralChoiceTerminal("smbios-field("), + RegexTerminal("[^)]+", "[^)]+"), + LiteralChoiceTerminal(")"), + ), + FlexibleLiteralChoiceTerminal("device-tree", "uefi"), + ) + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionFractionOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionFractionOptionValue.kt new file mode 100644 index 0000000..ce5378c --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionFractionOptionValue.kt @@ -0,0 +1,39 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PERMYRIAD_PERCENTAGE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString + +/* + * [Unit] ConditionFraction= / AssertFraction=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionFraction= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_FRACTION) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_fraction + */ + +/** + * Validator for `[Unit] ConditionFraction=` / `AssertFraction=`. + * + * C function: config_parse_unit_condition_string with ltype CONDITION_FRACTION. condition_test_fraction + * (src/shared/condition.c) reads `[TAG ]PERCENT`: the mandatory trailing field is a percentage parsed by + * parse_permyriad(), and an optional leading whitespace-separated field is a hash-salt tag (any word). + * More than two fields is "trailing garbage". + * + * The tag is any single non-whitespace token; the percentage is a [PERMYRIAD_PERCENTAGE]. The + * "tag + percent" alternative is listed first so it is tried before the bare percent (a lone token that + * fails to be a percentage is rejected, as systemd rejects it). + */ +class ConfigParseUnitConditionFractionOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString( + AlternativeCombinator( + SequenceCombinator(RegexTerminal("\\S+", "\\S+"), WhitespaceTerminal(), PERMYRIAD_PERCENTAGE), + PERMYRIAD_PERCENTAGE, + ) + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionGroupOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionGroupOptionValue.kt new file mode 100644 index 0000000..ce6a507 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionGroupOptionValue.kt @@ -0,0 +1,29 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString + +/* + * [Unit] ConditionGroup= / AssertGroup=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionGroup= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_GROUP) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_group + */ + +/** + * Validator for `[Unit] ConditionGroup=` / `AssertGroup=`. + * + * C function: config_parse_unit_condition_string with ltype CONDITION_GROUP. condition_test_group + * (src/shared/condition.c) accepts a numeric GID (parse_gid) or a group name (in_group / a `root` + * match under PID 1). Both forms are a non-empty run of characters with no `:` or `/`, no control + * characters, and no leading or trailing whitespace — the same lenient rule the plugin applies to + * User=/Group= (config_parse_user_group_compat), with a numeric GID a special case of that shape. + */ +class ConfigParseUnitConditionGroupOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString( + RegexTerminal(".+", "[^\\s:/\\x00-\\x1F](?:[^:/\\x00-\\x1F]*[^\\s:/\\x00-\\x1F])?") + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionHostOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionHostOptionValue.kt new file mode 100644 index 0000000..4cfa585 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionHostOptionValue.kt @@ -0,0 +1,26 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ANY_CONDITION_ARGUMENT +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString + +/* + * [Unit] ConditionHost= / AssertHost=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionHost= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_HOST) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_host + */ + +/** + * Validator for `[Unit] ConditionHost=` / `AssertHost=`. + * + * C function: config_parse_unit_condition_string with ltype CONDITION_HOST. condition_test_host + * (src/shared/condition.c) treats the parameter either as a 128-bit machine/boot/product ID or, failing + * that, as an fnmatch() glob against the hostname. Any non-empty string is a legitimate glob, so there + * is no shape to enforce beyond non-emptiness — see [ANY_CONDITION_ARGUMENT]. + */ +class ConfigParseUnitConditionHostOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString(ANY_CONDITION_ARGUMENT) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionKernelCommandLineOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionKernelCommandLineOptionValue.kt new file mode 100644 index 0000000..a70d0bb --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionKernelCommandLineOptionValue.kt @@ -0,0 +1,26 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ANY_CONDITION_ARGUMENT +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString + +/* + * [Unit] ConditionKernelCommandLine= / AssertKernelCommandLine=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionKernelCommandLine= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_KERNEL_COMMAND_LINE) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_kernel_command_line + */ + +/** + * Validator for `[Unit] ConditionKernelCommandLine=` / `AssertKernelCommandLine=`. + * + * C function: config_parse_unit_condition_string with ltype CONDITION_KERNEL_COMMAND_LINE. + * condition_test_kernel_command_line (src/shared/condition.c) matches the parameter against the words on + * the kernel command line — either a bare option name or a full `name=value`. Any non-empty string is a + * legitimate parameter, so only non-emptiness can be checked — see [ANY_CONDITION_ARGUMENT]. + */ +class ConfigParseUnitConditionKernelCommandLineOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString(ANY_CONDITION_ARGUMENT) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionKernelModuleLoadedOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionKernelModuleLoadedOptionValue.kt new file mode 100644 index 0000000..081b415 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionKernelModuleLoadedOptionValue.kt @@ -0,0 +1,32 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString + +/* + * [Unit] ConditionKernelModuleLoaded= / AssertKernelModuleLoaded=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionKernelModuleLoaded= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_KERNEL_MODULE_LOADED) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_kernel_module_loaded + * https://github.com/systemd/systemd/blob/048970650c/src/basic/path-util.c filename_is_valid + */ + +/** + * Validator for `[Unit] ConditionKernelModuleLoaded=` / `AssertKernelModuleLoaded=`. + * + * C function: config_parse_unit_condition_string with ltype CONDITION_KERNEL_MODULE_LOADED. + * condition_test_kernel_module_loaded (src/shared/condition.c) first replaces every `-` with `_` and + * then reports "not loaded" for any name filename_is_valid() rejects. filename_is_valid() + * (src/basic/path-util.c) requires: non-empty, not "." or "..", no "/", at most NAME_MAX (255) bytes. + * + * Because `-` is normalised to `_` before the check, a dash never turns a name into a reserved one, so + * only a bare `.` or `..` is refused. No character class beyond "no slash" is imposed by the parser. + */ +class ConfigParseUnitConditionKernelModuleLoadedOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString( + RegexTerminal(".+", "(?!\\.\\.?\$)[^/\\x00]{1,255}") + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionMachineTagOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionMachineTagOptionValue.kt new file mode 100644 index 0000000..eafc62e --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionMachineTagOptionValue.kt @@ -0,0 +1,26 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ANY_CONDITION_ARGUMENT +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString + +/* + * [Unit] ConditionMachineTag= / AssertMachineTag=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionMachineTag= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_MACHINE_TAG) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_machine_tag + */ + +/** + * Validator for `[Unit] ConditionMachineTag=` / `AssertMachineTag=`. + * + * C function: config_parse_unit_condition_string with ltype CONDITION_MACHINE_TAG. condition_test_machine_tag + * (src/shared/condition.c) uses the parameter as an fnmatch() glob against the TAGS from /etc/machine-info. + * Any non-empty string is a legitimate glob, so nothing beyond non-emptiness can be checked — see + * [ANY_CONDITION_ARGUMENT]. + */ +class ConfigParseUnitConditionMachineTagOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString(ANY_CONDITION_ARGUMENT) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionMemoryOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionMemoryOptionValue.kt new file mode 100644 index 0000000..bbcfbab --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionMemoryOptionValue.kt @@ -0,0 +1,34 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ByteSizeTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.comparisonPrefixed +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString +import java.math.BigInteger + +/* + * [Unit] ConditionMemory= / AssertMemory=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionMemory= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_MEMORY) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_memory + */ + +/** + * Validator for `[Unit] ConditionMemory=` / `AssertMemory=`. + * + * C function: config_parse_unit_condition_string with ltype CONDITION_MEMORY. condition_test_memory + * (src/shared/condition.c) reads an optional comparison operator via parse_compare_operator(&p, 0) — + * defaulting to `>=` — and then a byte count with parse_size(p, 1024). [ByteSizeTerminal] models that + * single-element size (base 1024, `K`/`M`/`G`/… suffixes, optional fractional part). The value is + * only compared against physical memory, so any non-negative size is accepted; the bound here is the + * full unsigned-64 range parse_size() itself can represent. + */ +class ConfigParseUnitConditionMemoryOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString(comparisonPrefixed(ByteSizeTerminal(BigInteger.ZERO, UINT64_MAX))) +) { + companion object { + private val UINT64_MAX: BigInteger = BigInteger.TWO.pow(64).subtract(BigInteger.ONE) + } +} diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionOsReleaseOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionOsReleaseOptionValue.kt new file mode 100644 index 0000000..0ab3b4e --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionOsReleaseOptionValue.kt @@ -0,0 +1,54 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ENV_NAME +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FNMATCH_COMPARE_OPERATOR +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString + +/* + * [Unit] ConditionOSRelease= / AssertOSRelease=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionOSRelease= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_OS_RELEASE) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_osrelease + */ + +// A quoted expression (either quote style). The parser unquotes each whitespace-separated token before +// looking at it, so a quoted token may legally contain spaces; matched permissively rather than +// re-deriving the key/op/value split inside quotes, so a quoted value with spaces is never mis-flagged. +private val OSRELEASE_QUOTED = RegexTerminal("\"[^\"]*\"|'[^']*'", "\"[^\"]*\"|'[^']*'") + +// One bare `KEYVALUE` expression with no internal whitespace: an env-var-valid key, a comparison +// operator (with fnmatch), then a non-empty value. condition_test_osrelease rejects whitespace after the +// operator, so the three parts are contiguous. +private val OSRELEASE_BARE_EXPR = SequenceCombinator(ENV_NAME, FNMATCH_COMPARE_OPERATOR, RegexTerminal("\\S+", "\\S+")) + +private val OSRELEASE_EXPR = AlternativeCombinator(OSRELEASE_QUOTED, OSRELEASE_BARE_EXPR) + +/** + * Validator for `[Unit] ConditionOSRelease=` / `AssertOSRelease=`. + * + * C function: config_parse_unit_condition_string with ltype CONDITION_OS_RELEASE. condition_test_osrelease + * (src/shared/condition.c) reads a whitespace-separated list of `KEYVALUE` expressions. For each + * token it requires: a key that env_name_is_valid() accepts, one of the comparison operators + * (parse_compare_operator with fnmatch), no whitespace after the operator, and a non-empty value. + * + * Because the tokens are extracted with EXTRACT_UNQUOTE, a token may be quoted and then legally contain + * spaces in its value; that form is accepted permissively rather than split, so a value with spaces is + * never wrongly flagged. The bare form enforces the full key/op/value shape and so still catches the + * real mistakes — a token with no operator, or a key that starts with a digit. + */ +class ConfigParseUnitConditionOsReleaseOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString( + SequenceCombinator( + OSRELEASE_EXPR, + ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), OSRELEASE_EXPR)), + ) + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionPressureOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionPressureOptionValue.kt new file mode 100644 index 0000000..6b8655e --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionPressureOptionValue.kt @@ -0,0 +1,55 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.LiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PERMYRIAD_PERCENTAGE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString + +/* + * [Unit] ConditionMemoryPressure= / ConditionCPUPressure= / ConditionIOPressure= (and Assert twins). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionMemoryPressure= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_*_PRESSURE) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_psi + */ + +private val PSI_OPTIONAL_WHITESPACE = ZeroOrOne(WhitespaceTerminal()) + +// A cgroup slice, e.g. "system.slice" or "user-1000.slice"; taken verbatim (strstrip'd) and handed to +// cg_slice_to_path. Modelled loosely as a run of non-whitespace, non-colon characters. +private val PSI_SLICE = RegexTerminal("[^\\s:]+", "[^\\s:]+") + +// The averaging window: condition_test_psi only checks startswith on "10sec"/"1min"/"5min", so any +// trailing text after one of those prefixes is accepted too. +private val PSI_TIMESPAN = RegexTerminal("(?:10sec|1min|5min).*", "(?:10sec|1min|5min).*") + +/** + * Validator for `[Unit] ConditionMemoryPressure=` / `ConditionCPUPressure=` / `ConditionIOPressure=` + * and their `Assert…=` twins (all three share the single leaf parser condition_test_psi). + * + * C function: config_parse_unit_condition_string with ltype CONDITION_MEMORY_PRESSURE / + * CONDITION_CPU_PRESSURE / CONDITION_IO_PRESSURE. condition_test_psi (src/shared/condition.c) parses + * `[SLICE:]THRESHOLD[/TIMESPAN]`: + * + * - the value is split once on `:` — a leading field, if present, is a cgroup slice, else the check is + * against global pressure; + * - the threshold is split once on `/` into a percentage (parse_permyriad) and an optional averaging + * window that must begin `10sec`, `1min` or `5min`. + * + * The `:` and `/` fields are strstrip'd, so whitespace is allowed around those separators but not + * between the number and its `%` — matching how the kernel-facing parser reads it. + */ +class ConfigParseUnitConditionPressureOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString( + SequenceCombinator( + ZeroOrOne(SequenceCombinator(PSI_SLICE, PSI_OPTIONAL_WHITESPACE, LiteralChoiceTerminal(":"), PSI_OPTIONAL_WHITESPACE)), + PERMYRIAD_PERCENTAGE, + ZeroOrOne(SequenceCombinator(PSI_OPTIONAL_WHITESPACE, LiteralChoiceTerminal("/"), PSI_OPTIONAL_WHITESPACE, PSI_TIMESPAN)), + ) + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionUserOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionUserOptionValue.kt new file mode 100644 index 0000000..fc771ba --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionUserOptionValue.kt @@ -0,0 +1,34 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString + +/* + * [Unit] ConditionUser= / AssertUser=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionUser= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_USER) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_user + */ + +/** + * Validator for `[Unit] ConditionUser=` / `AssertUser=`. + * + * C function: config_parse_unit_condition_string with ltype CONDITION_USER. condition_test_user + * (src/shared/condition.c) accepts the literal `@system`, a numeric UID (parse_uid), or a user name + * (matched against the running user's name / looked up with get_user_creds). `root` and the configured + * nobody name are just user names handled by that last path. + * + * All of those forms are a non-empty run of characters with no `:` or `/`, no control characters, and + * no leading or trailing whitespace — the same lenient rule the plugin already applies to User=/Group= + * (config_parse_user_group_compat). A numeric UID and `@system` are special cases of that shape, so one + * terminal covers every accepted value without an alternation that could shadow a name beginning + * `@system…`. + */ +class ConfigParseUnitConditionUserOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString( + RegexTerminal(".+", "[^\\s:/\\x00-\\x1F](?:[^:/\\x00-\\x1F]*[^\\s:/\\x00-\\x1F])?") + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionVersionOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionVersionOptionValue.kt new file mode 100644 index 0000000..17aa788 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionVersionOptionValue.kt @@ -0,0 +1,28 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ANY_CONDITION_ARGUMENT +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString + +/* + * [Unit] ConditionKernelVersion= / ConditionVersion= (and their Assert twins). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionKernelVersion= + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_VERSION) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_version, condition_test_version_cmp + */ + +/** + * Validator for `[Unit] ConditionKernelVersion=` / `ConditionVersion=` / `AssertKernelVersion=` / + * `AssertVersion=` (all share ltype CONDITION_VERSION). + * + * C function: config_parse_unit_condition_string with ltype CONDITION_VERSION. condition_test_version / + * condition_test_version_cmp (src/shared/condition.c) accept a whitespace-separated list of + * `[operator] value` expressions, but when an expression carries no comparison operator it is treated as + * a plain fnmatch glob — and any non-empty token is a valid glob. There is therefore no non-empty value + * the parser rejects, so the honest model is non-emptiness — see [ANY_CONDITION_ARGUMENT]. + */ +class ConfigParseUnitConditionVersionOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_condition_string", + conditionString(ANY_CONDITION_ARGUMENT) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Combinators.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Combinators.kt index 1128e57..0d53f87 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Combinators.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Combinators.kt @@ -370,3 +370,66 @@ val CAPABILITY_NAME = FlexibleLiteralChoiceTerminal( *SimpleGrammarOptionValues.Capabilities.choices, ignoreCase = true, ) + + +// --------------------------------------------------------------------------------------------------- +// Comparison operators — parse_compare_operator (systemd src/shared/compare-operator.c). +// +// This is the set that helper recognises when called with *no* flags, i.e. neither COMPARE_ALLOW_FNMATCH, +// COMPARE_EQUAL_BY_STRING nor COMPARE_ALLOW_TEXTUAL. The fnmatch (`$=`, `!$=`) and textual (`lt`, `ge`, …) +// spellings need their flag to be set, and the string-vs-order `=`/`!=` distinction needs +// COMPARE_EQUAL_BY_STRING, so with no flags `=` and `!=` fall through to the ordinary equal/unequal +// operators. That is exactly how config_parse's numeric conditions call it — ConditionMemory= and +// ConditionCPUs= both pass 0 (src/shared/condition.c, condition_test_memory / condition_test_cpus). +// +// LiteralChoiceTerminal sorts its choices longest-first, so `<=` is tried before `<` automatically. +val ORDER_COMPARE_OPERATOR = LiteralChoiceTerminal("<>", "<=", ">=", "==", "!=", "<", ">", "=") + +/** + * A value optionally prefixed by an [ORDER_COMPARE_OPERATOR], as the numeric `Condition…=` settings read + * it: `parse_compare_operator()` is tried first and, if no operator is present, the check defaults to + * `>=` (condition_test_memory / condition_test_cpus). The operator does not consume trailing whitespace, + * but `parse_size()` / `safe_atou()` are lenient about a leading space, so an optional gap is allowed + * between the two rather than risk flagging a spelling systemd accepts. + */ +fun comparisonPrefixed(value: Combinator): Combinator = SequenceCombinator( + ZeroOrOne(SequenceCombinator(ORDER_COMPARE_OPERATOR, ZeroOrOne(WhitespaceTerminal()))), + value, +) + +/** + * Any non-empty value, for a `Condition…=` whose parameter systemd stores verbatim and only interprets + * when the unit is tested, never at config-parse time. + * + * config_parse_unit_condition_string does no per-condition validation — it strips the `|`/`!` markers, + * resolves specifiers, and stores the rest. For conditions like ConditionHost= (an fnmatch glob or a + * machine/boot-ID), ConditionKernelCommandLine= and ConditionEnvironment= (a bare word or `word=value`), + * ConditionMachineTag= (an fnmatch glob) and ConditionKernelVersion= (whose bare, operator-less form is + * treated as a glob), any non-empty string is legitimate. Matching anything narrower here would flag + * values systemd happily accepts, so the honest model is "not empty". The empty value is handled upstream + * as a list reset and never reaches the grammar. + */ +val ANY_CONDITION_ARGUMENT = RegexTerminal(".+", ".+") + + +// --------------------------------------------------------------------------------------------------- +// Percentages — parse_permyriad (systemd src/basic/percent-util.c). An integer with an optional +// fractional part and a unit symbol: "%" allows up to two decimal places, "‰" up to one, "‱" none. +// parse_permyriad caps the value at 100% (10000 permyriad); that ceiling is deliberately NOT enforced +// here — bounding the decimal forms in a regex would wrongly reject legal leading-zero spellings like +// `050%`, and over-flagging is worse than the occasional missed `150%`. Only the shape is checked. +val PERMYRIAD_PERCENTAGE = RegexTerminal( + "[0-9]+(?:\\.[0-9]+)?[%‰‱]", + "[0-9]+(?:\\.[0-9]{1,2})?%|[0-9]+(?:\\.[0-9])?‰|[0-9]+‱", +) + +// Comparison operators as parse_compare_operator recognises them with COMPARE_ALLOW_FNMATCH | +// COMPARE_EQUAL_BY_STRING (src/shared/compare-operator.c) — the flags condition_test_osrelease and the +// SMBIOS/version comparisons pass. Adds the fnmatch `$=` / `!$=` forms to the ordinary ordering +// operators; the textual `lt`/`le`/… spellings still need COMPARE_ALLOW_TEXTUAL and so are absent. +// LiteralChoiceTerminal sorts its choices longest-first, so `!$=` is tried before `!=` automatically. +val FNMATCH_COMPARE_OPERATOR = LiteralChoiceTerminal("!$=", "$=", "<>", "<=", ">=", "==", "!=", "<", ">", "=") + +// An environment-variable name — env_name_is_valid (systemd src/basic/env-util.c): a letter or +// underscore followed by letters, digits and underscores. Used by ConditionOSRelease= keys. +val ENV_NAME = RegexTerminal("[A-Za-z_][A-Za-z0-9_]*", "[A-Za-z_][A-Za-z0-9_]*") diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionCpusOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionCpusOptionValueTest.kt new file mode 100644 index 0000000..a34c4aa --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionCpusOptionValueTest.kt @@ -0,0 +1,67 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionCpusOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionCPUs=2 + ConditionCPUs=>=4 + ConditionCPUs=<=8 + ConditionCPUs=<2 + ConditionCPUs=>16 + ConditionCPUs===1 + ConditionCPUs=!=0 + ConditionCPUs=<>3 + ConditionCPUs=>= 4 + ConditionCPUs=0x10 + AssertCPUs=8 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testValidWithMarkers() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionCPUs=!2 + ConditionCPUs=|4 + ConditionCPUs=|!8 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionCPUs=abc + ConditionCPUs=1.5 + ConditionCPUs=>= + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionCredentialOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionCredentialOptionValueTest.kt new file mode 100644 index 0000000..50c4f3a --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionCredentialOptionValueTest.kt @@ -0,0 +1,45 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionCredentialOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionCredential=mycred + ConditionCredential=my.cred + ConditionCredential=my-cred_1 + ConditionCredential=some cred + AssertCredential=other + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionCredential=foo/bar + ConditionCredential=foo:bar + ConditionCredential=. + ConditionCredential=.. + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(4, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionEnvironmentOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionEnvironmentOptionValueTest.kt new file mode 100644 index 0000000..cbf95fa --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionEnvironmentOptionValueTest.kt @@ -0,0 +1,45 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionEnvironmentOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Either a bare variable name or a full NAME=value; the parameter is compared verbatim, so any + // non-empty string is legitimate. + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionEnvironment=MY_VAR + ConditionEnvironment=MY_VAR=1 + ConditionEnvironment=PATH=/usr/bin + AssertEnvironment=DEBUG=true + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testValidWithMarkers() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionEnvironment=!MY_VAR + ConditionEnvironment=|MY_VAR + ConditionEnvironment=|!MY_VAR=1 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionFirmwareOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionFirmwareOptionValueTest.kt new file mode 100644 index 0000000..b97499e --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionFirmwareOptionValueTest.kt @@ -0,0 +1,61 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionFirmwareOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionFirmware=uefi + ConditionFirmware=device-tree + ConditionFirmware=device-tree-compatible(foo,bar) + ConditionFirmware=smbios-field(board_vendor = foo) + AssertFirmware=uefi + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testValidWithMarkers() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionFirmware=!uefi + ConditionFirmware=|device-tree + ConditionFirmware=|!uefi + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionFirmware=bios + ConditionFirmware=device-treex + ConditionFirmware=device-tree-compatible + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionFractionOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionFractionOptionValueTest.kt new file mode 100644 index 0000000..103b7c7 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionFractionOptionValueTest.kt @@ -0,0 +1,63 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionFractionOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionFraction=50% + ConditionFraction=0% + ConditionFraction=100% + ConditionFraction=12.5% + ConditionFraction=canary 25% + ConditionFraction=rollout-a 1.25% + AssertFraction=10% + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testValidWithMarkers() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionFraction=!50% + ConditionFraction=|25% + ConditionFraction=|!canary 25% + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionFraction=canary + ConditionFraction=50 + ConditionFraction=foo bar baz + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionGroupOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionGroupOptionValueTest.kt new file mode 100644 index 0000000..74cd21b --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionGroupOptionValueTest.kt @@ -0,0 +1,43 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionGroupOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionGroup=root + ConditionGroup=1000 + ConditionGroup=mygroup + ConditionGroup=my-group + AssertGroup=0 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionGroup=foo:bar + ConditionGroup=foo/bar + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(2, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionHostOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionHostOptionValueTest.kt new file mode 100644 index 0000000..18ce4fd --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionHostOptionValueTest.kt @@ -0,0 +1,45 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionHostOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // A host condition is a hostname, a machine/boot/product ID, or an fnmatch glob: any non-empty + // string is legitimate, so none of these are flagged. + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionHost=myhostname + ConditionHost=web-* + ConditionHost=4db8f6a1e2c04b3d9a7e5f10c3b2a1d0 + AssertHost=*.example.com + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testValidWithMarkers() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionHost=!myhost + ConditionHost=|myhost + ConditionHost=|!myhost + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionKernelCommandLineOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionKernelCommandLineOptionValueTest.kt new file mode 100644 index 0000000..7d8f248 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionKernelCommandLineOptionValueTest.kt @@ -0,0 +1,45 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionKernelCommandLineOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Either a bare kernel-command-line option or a full name=value; the parameter is stored verbatim, + // so any non-empty string is legitimate. + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionKernelCommandLine=quiet + ConditionKernelCommandLine=debug + ConditionKernelCommandLine=systemd.unified_cgroup_hierarchy=1 + AssertKernelCommandLine=rd.rescue + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testValidWithMarkers() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionKernelCommandLine=!quiet + ConditionKernelCommandLine=|debug + ConditionKernelCommandLine=|!quiet + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionKernelModuleLoadedOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionKernelModuleLoadedOptionValueTest.kt new file mode 100644 index 0000000..26f8c94 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionKernelModuleLoadedOptionValueTest.kt @@ -0,0 +1,44 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionKernelModuleLoadedOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionKernelModuleLoaded=nf_tables + ConditionKernelModuleLoaded=nf-tables + ConditionKernelModuleLoaded=overlay + ConditionKernelModuleLoaded=foo.bar + AssertKernelModuleLoaded=btrfs + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionKernelModuleLoaded=foo/bar + ConditionKernelModuleLoaded=. + ConditionKernelModuleLoaded=.. + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionMachineTagOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionMachineTagOptionValueTest.kt new file mode 100644 index 0000000..27e830b --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionMachineTagOptionValueTest.kt @@ -0,0 +1,44 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionMachineTagOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // A machine tag is matched with fnmatch against /etc/machine-info TAGS: any non-empty string is + // a legitimate glob, so none of these are flagged. + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionMachineTag=production + ConditionMachineTag=edge-* + AssertMachineTag=staging + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testValidWithMarkers() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionMachineTag=!production + ConditionMachineTag=|production + ConditionMachineTag=|!production + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionMemoryOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionMemoryOptionValueTest.kt new file mode 100644 index 0000000..3623295 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionMemoryOptionValueTest.kt @@ -0,0 +1,64 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionMemoryOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionMemory=1G + ConditionMemory=>=512M + ConditionMemory=<=1024 + ConditionMemory=100M + ConditionMemory=>1K + ConditionMemory=>= 2G + ConditionMemory=1.5G + AssertMemory=8G + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testValidWithMarkers() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionMemory=!1G + ConditionMemory=|2G + ConditionMemory=|!512M + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionMemory=abc + ConditionMemory=1Q + ConditionMemory=1.5.2 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionOsReleaseOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionOsReleaseOptionValueTest.kt new file mode 100644 index 0000000..bc48ca3 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionOsReleaseOptionValueTest.kt @@ -0,0 +1,63 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionOsReleaseOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionOSRelease=ID=fedora + ConditionOSRelease=ID=some-id + ConditionOSRelease=VERSION_ID>=30 + ConditionOSRelease=ID=fedora VARIANT_ID=workstation + ConditionOSRelease=NAME${'$'}=Fedora* + ConditionOSRelease="PRETTY_NAME=Some Thing" + AssertOSRelease=ID!=debian + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testValidWithMarkers() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionOSRelease=!ID=fedora + ConditionOSRelease=|ID=fedora + ConditionOSRelease=|!VERSION_ID>=30 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionOSRelease=fedora + ConditionOSRelease=123=x + ConditionOSRelease=ID = fedora + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionPressureOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionPressureOptionValueTest.kt new file mode 100644 index 0000000..84c592d --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionPressureOptionValueTest.kt @@ -0,0 +1,66 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionPressureOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Covers all three pressure ltypes (memory/cpu/io) plus the slice, spacing, and timespan forms. + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionMemoryPressure=20%/5min + ConditionMemoryPressure=10%/10sec + ConditionMemoryPressure=50% + ConditionCPUPressure=12%/1min + ConditionIOPressure=30% / 1min + ConditionMemoryPressure=system.slice:20%/5min + ConditionMemoryPressure=system.slice : 40% + AssertCPUPressure=12% + AssertIOPressure=30%/5min + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testValidWithMarkers() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionMemoryPressure=!20%/5min + ConditionMemoryPressure=|50% + ConditionMemoryPressure=|!30%/1min + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionMemoryPressure=20%/5sec + ConditionMemoryPressure=30 %/1min + ConditionMemoryPressure=abc + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionUserOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionUserOptionValueTest.kt new file mode 100644 index 0000000..ce79c37 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionUserOptionValueTest.kt @@ -0,0 +1,44 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionUserOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionUser=root + ConditionUser=1000 + ConditionUser=@system + ConditionUser=nobody + ConditionUser=my-user + AssertUser=0 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionUser=foo:bar + ConditionUser=foo/bar + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(2, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionVersionOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionVersionOptionValueTest.kt new file mode 100644 index 0000000..4ff733d --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitConditionVersionOptionValueTest.kt @@ -0,0 +1,47 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUnitConditionVersionOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // An operator-less token is treated as an fnmatch glob, so any non-empty string is legitimate. + // Covers both ConditionKernelVersion= and ConditionVersion= (shared ltype CONDITION_VERSION). + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionKernelVersion=>=5.4 + ConditionKernelVersion=6.* + ConditionVersion=systemd >= 250 + ConditionVersion=glibc >= 2.31 + AssertKernelVersion=<6.0 + AssertVersion=5.15 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testValidWithMarkers() { + // language="unit file (systemd)" + val file = """ + [Unit] + ConditionKernelVersion=!5.4 + ConditionKernelVersion=|6.1 + ConditionKernelVersion=|!5.10 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/OptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/OptionValueTest.kt index e04b2ce..6eda9c5 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/OptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/OptionValueTest.kt @@ -34,13 +34,8 @@ class OptionValueTest : AbstractUnitFileTest() { } } val missingValidatorList = missingValidators.map { "${String.format("%05d", it.value)}, ${it.key}" } - val sortedList = missingValidatorList.sortedDescending().joinToString("\n") - println("Missing:$totalMissingValidators") - println("Missing Functions:${missingValidators.size}") - println("Found:$totalFoundValidators") - val startDate = LocalDate.of(2025, 10, 27) // Today's date val startingCount = 619 // Your current undocumented options count val currentDate = LocalDate.now() @@ -48,8 +43,13 @@ class OptionValueTest : AbstractUnitFileTest() { val reductionPerDay = 1 val allowed = maxOf(0, startingCount - (daysSinceStart * reductionPerDay)) + println("Missing Keywords:$totalMissingValidators") + println("Missing Validators:${missingValidators.size}") + println("Allowed Missing Validation: $allowed") + println("Found:$totalFoundValidators") + if (missingValidators.size >= allowed) { - assertEquals("Number of missing functions is too high at ${missingValidators.size} > $allowed vs. found ${foundValidators.size} ${totalFoundValidators}", sortedList, "") + assertEquals("Number of missing validators is too high at ${missingValidators.size} > $allowed vs. found ${foundValidators.size} ${totalFoundValidators}", sortedList, "") } if (totalFoundValidators == 0) {