From 3f843b6e4eb77688754b62c05c14824a8315f4d7 Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Sat, 23 May 2026 10:27:08 -0700 Subject: [PATCH 1/3] feat: add 6 validators - cpu_quota, htb_class_size, delegate, syscall_errno, address_families, pass_environ (Resolves #446) Batch 2/5 of the burn-down extension. Each grammar mirrors the systemd C parser: - config_parse_cpu_quota: parse_permyriad_unbounded, percent form unbounded (200% valid) - config_parse_htb_class_size (TCLASS_KIND_HTB): IEC byte size, same shape as tbf_size/fq_size - config_parse_delegate: boolean OR whitespace-separated cgroup controller names - config_parse_syscall_errno: "kill" OR errno name (E*) OR integer - config_parse_address_families: "none" OR optional ~ + list of AF_* names - config_parse_pass_environ: list of env var names (specifier-aware) Note for future validators: when an Alternative contains both a FlexibleLiteralChoiceTerminal (like BOOLEAN) and a more specific structured branch, put the structured branch first. FlexibleLiteralChoice's syntactic phase accepts any short alphanumeric token in its character class, so a single-token input like "cpu" would syntactically pass BOOLEAN and AlternativeCombinator would short-circuit before reaching the structured branch. OptionValueTest missing-function count drops 394 -> 388; found-key count rises 1780 -> 1812. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../semanticdata/optionvalues/AiGenerated.kt | 6 +++ .../ConfigParseAddressFamiliesOptionValue.kt | 35 +++++++++++++ .../ai/ConfigParseCpuQuotaOptionValue.kt | 25 ++++++++++ .../ai/ConfigParseDelegateOptionValue.kt | 41 +++++++++++++++ .../ai/ConfigParseHtbClassSizeOptionValue.kt | 24 +++++++++ .../ai/ConfigParsePassEnvironOptionValue.kt | 29 +++++++++++ .../ai/ConfigParseSyscallErrnoOptionValue.kt | 29 +++++++++++ ...nfigParseAddressFamiliesOptionValueTest.kt | 48 ++++++++++++++++++ .../ai/ConfigParseCpuQuotaOptionValueTest.kt | 49 ++++++++++++++++++ .../ai/ConfigParseDelegateOptionValueTest.kt | 49 ++++++++++++++++++ .../ConfigParseHtbClassSizeOptionValueTest.kt | 45 +++++++++++++++++ .../ConfigParsePassEnvironOptionValueTest.kt | 48 ++++++++++++++++++ .../ConfigParseSyscallErrnoOptionValueTest.kt | 50 +++++++++++++++++++ 13 files changed, 478 insertions(+) create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseAddressFamiliesOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCpuQuotaOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDelegateOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassSizeOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePassEnvironOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSyscallErrnoOptionValue.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseAddressFamiliesOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCpuQuotaOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDelegateOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassSizeOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePassEnvironOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseSyscallErrnoOptionValueTest.kt 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 6f5b38d..20538c8 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 @@ -12,6 +12,7 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_6rd_prefix", "0") to ConfigParse6rdPrefixOptionValue() as OptionValueInformation, Validator("config_parse_ad_actor_sys_prio", "0") to ConfigParseAdActorSysPrioOptionValue() as OptionValueInformation, Validator("config_parse_ad_user_port_key", "0") to ConfigParseAdUserPortKeyOptionValue() as OptionValueInformation, + Validator("config_parse_address_families", "0") to ConfigParseAddressFamiliesOptionValue() as OptionValueInformation, Validator("config_parse_address_section", "ADDRESS_ADD_PREFIX_ROUTE") to ConfigParseAddressSectionOptionValue() as OptionValueInformation, Validator("config_parse_address_section", "ADDRESS_AUTO_JOIN") to ConfigParseAddressSectionOptionValue() as OptionValueInformation, Validator("config_parse_address_section", "ADDRESS_DAD") to ConfigParseAddressSectionOptionValue() as OptionValueInformation, @@ -54,7 +55,9 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_codel_bool", "QDISC_KIND_CODEL") to ConfigParseCodelBoolOptionValue() as OptionValueInformation, Validator("config_parse_codel_u32", "QDISC_KIND_CODEL") to ConfigParseCodelU32OptionValue() as OptionValueInformation, Validator("config_parse_collect_mode", "0") to ConfigParseCollectModeOptionValue() as OptionValueInformation, + Validator("config_parse_cpu_quota", "0") to ConfigParseCpuQuotaOptionValue() as OptionValueInformation, Validator("config_parse_cpuset_partition", "0") to ConfigParseCpusetPartitionOptionValue() as OptionValueInformation, + Validator("config_parse_delegate", "0") to ConfigParseDelegateOptionValue() as OptionValueInformation, Validator("config_parse_df", "0") to ConfigParseDfOptionValue() as OptionValueInformation, Validator("config_parse_dhcp6_client_start_mode", "0") to ConfigParseDhcp6ClientStartModeOptionValue() as OptionValueInformation, Validator("config_parse_dhcp6_pd_prefix_hint", "0") to ConfigParseDhcp6PdPrefixHintOptionValue() as OptionValueInformation, @@ -96,6 +99,7 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_geneve_ttl", "0") to ConfigParseGeneveTtlOptionValue() as OptionValueInformation, Validator("config_parse_geneve_vni", "0") to ConfigParseGeneveVniOptionValue() as OptionValueInformation, Validator("config_parse_hhf_packet_limit", "QDISC_KIND_HHF") to ConfigParseHhfPacketLimitOptionValue() as OptionValueInformation, + Validator("config_parse_htb_class_size", "TCLASS_KIND_HTB") to ConfigParseHtbClassSizeOptionValue() as OptionValueInformation, Validator("config_parse_iaid", "AF_INET6") to ConfigParseIaidOptionValue() as OptionValueInformation, Validator("config_parse_iaid", "AF_INET") to ConfigParseIaidOptionValue() as OptionValueInformation, Validator("config_parse_in_addr_non_null", "AF_INET") to ConfigParseInAddrNonNullOptionValue() as OptionValueInformation, @@ -138,6 +142,7 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_nexthop_section", "NEXTHOP_ID") to ConfigParseNexthopSectionOptionValue() as OptionValueInformation, Validator("config_parse_nexthop_section", "NEXTHOP_ONLINK") to ConfigParseNexthopSectionOptionValue() as OptionValueInformation, Validator("config_parse_nsec", "0") to ConfigParseNsecOptionValue() as OptionValueInformation, + Validator("config_parse_pass_environ", "0") to ConfigParsePassEnvironOptionValue() as OptionValueInformation, Validator("config_parse_permille", "0") to ConfigParsePermilleOptionValue() as OptionValueInformation, Validator("config_parse_pfifo_size", "QDISC_KIND_PFIFO") to ConfigParsePfifoSizeOptionValue() as OptionValueInformation, Validator("config_parse_pfifo_size", "QDISC_KIND_PFIFO_HEAD_DROP") to ConfigParsePfifoSizeOptionValue() as OptionValueInformation, @@ -175,6 +180,7 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_sr_iov_uint32", "0") to ConfigParseSrIovUint32OptionValue() as OptionValueInformation, Validator("config_parse_sr_iov_vlan_proto", "0") to ConfigParseSrIovVlanProtoOptionValue() as OptionValueInformation, Validator("config_parse_swap_priority", "0") to ConfigParseSwapPriorityOptionValue() as OptionValueInformation, + Validator("config_parse_syscall_errno", "0") to ConfigParseSyscallErrnoOptionValue() as OptionValueInformation, Validator("config_parse_tasks_max", "0") to ConfigParseTasksMaxOptionValue() as OptionValueInformation, Validator("config_parse_tbf_size", "QDISC_KIND_TBF") to ConfigParseTbfSizeOptionValue() as OptionValueInformation, Validator("config_parse_tcp_window", "0") to ConfigParseTcpWindowOptionValue() as OptionValueInformation, diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseAddressFamiliesOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseAddressFamiliesOptionValue.kt new file mode 100644 index 0000000..186890f --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseAddressFamiliesOptionValue.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.* + +/** + * Validator for RestrictAddressFamilies=. + * + * C function: config_parse_address_families in src/core/load-fragment.c → parse_address_families + * in src/shared/parse-helpers.c:90. Accepts: + * - "none" (clears the set, sets allowlist) + * - optional leading "~" (invert / denylist mode), followed by a whitespace-separated list + * of address family names from af_from_name (AF_UNIX, AF_INET, AF_INET6, AF_NETLINK, + * AF_PACKET, …) + * + * The grammar matches the "AF_" prefix loosely (any uppercase/digit/underscore tail); unknown + * names slip past the grammar but fail at runtime. This is the same tradeoff as syscall_errno. + */ +class ConfigParseAddressFamiliesOptionValue : SimpleGrammarOptionValues( + "config_parse_address_families", + SequenceCombinator( + AlternativeCombinator( + LiteralChoiceTerminal("none"), + SequenceCombinator( + ZeroOrOne(LiteralChoiceTerminal("~")), + RegexTerminal("AF_[A-Z0-9_]+", "AF_[A-Z0-9_]+"), + ZeroOrMore(SequenceCombinator( + WhitespaceTerminal(), + RegexTerminal("AF_[A-Z0-9_]+", "AF_[A-Z0-9_]+") + )) + ) + ), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCpuQuotaOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCpuQuotaOptionValue.kt new file mode 100644 index 0000000..e8d25ab --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCpuQuotaOptionValue.kt @@ -0,0 +1,25 @@ +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.* + +/** + * Validator for CPUQuota=. + * + * C function: config_parse_cpu_quota in src/core/load-fragment.c. After the empty-input check + * it calls parse_permyriad_unbounded(rvalue), which accepts N% / N.N% / N.NN% (with no upper + * bound — values over 100% are meaningful here, e.g. "200%" means 2 cores). The result must + * be strictly greater than zero, so "0%", "0.0%", "0.00%" are rejected at the C semantic + * layer; the grammar accepts them but the runtime parser will warn. + * + * Only the ASCII percent form is matched here; the ‰/‱ Unicode suffixes are vanishingly rare + * in unit files. + */ +class ConfigParseCpuQuotaOptionValue : SimpleGrammarOptionValues( + "config_parse_cpu_quota", + SequenceCombinator( + RegexTerminal("[0-9]+(\\.[0-9]{1,2})?", "[0-9]+(\\.[0-9]{1,2})?"), + LiteralChoiceTerminal("%"), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDelegateOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDelegateOptionValue.kt new file mode 100644 index 0000000..ce1db4f --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDelegateOptionValue.kt @@ -0,0 +1,41 @@ +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.* + +/** + * Validator for Delegate=. + * + * C function: config_parse_delegate in src/core/load-fragment.c. Either a boolean (toggles + * delegation for all controllers) or a whitespace-separated list of cgroup controller names + * from cgroup_controller_table in src/basic/cgroup-util.c. + * + * Note: the controller list is the first alternative because BOOLEAN is a + * FlexibleLiteralChoiceTerminal whose syntactic phase accepts any short alphanumeric token — + * if it ran first, single-controller inputs like "cpu" would syntactically consume the value + * as a (fake) boolean and AlternativeCombinator would short-circuit there. + */ +class ConfigParseDelegateOptionValue : SimpleGrammarOptionValues( + "config_parse_delegate", + SequenceCombinator( + AlternativeCombinator( + SequenceCombinator( + LiteralChoiceTerminal( + "cpu", "cpuacct", "cpuset", "io", "blkio", "memory", "devices", "pids", + "bpf-firewall", "bpf-devices", "bpf-foreign", "bpf-socket-bind", + "bpf-restrict-network-interfaces", "bpf-bind-network-interface" + ), + ZeroOrMore(SequenceCombinator( + WhitespaceTerminal(), + LiteralChoiceTerminal( + "cpu", "cpuacct", "cpuset", "io", "blkio", "memory", "devices", "pids", + "bpf-firewall", "bpf-devices", "bpf-foreign", "bpf-socket-bind", + "bpf-restrict-network-interfaces", "bpf-bind-network-interface" + ) + )) + ), + BOOLEAN + ), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassSizeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassSizeOptionValue.kt new file mode 100644 index 0000000..5e4a561 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassSizeOptionValue.kt @@ -0,0 +1,24 @@ +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.* + +/** + * Validator for the HTB class size-style options: QuantumBytes=, MTUBytes=, OverheadBytes=, + * BufferBytes=, CeilBufferBytes=. + * + * C function: config_parse_htb_class_size(TCLASS_KIND_HTB) in src/network/tc/htb.c. After + * branching on the lvalue, it calls parse_size(rvalue, 1024, &v), so the value is a decimal + * byte count optionally suffixed with an IEC unit. Same shape as config_parse_tbf_size and + * config_parse_fq_size. + * + * The per-lvalue OverheadBytes <= UINT16_MAX bound and the general v <= UINT32_MAX bound are + * semantic checks that can't be expressed at the grammar level without lvalue access. + */ +class ConfigParseHtbClassSizeOptionValue : SimpleGrammarOptionValues( + "config_parse_htb_class_size", + SequenceCombinator( + OptionalWhitespacePrefix(BYTES), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePassEnvironOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePassEnvironOptionValue.kt new file mode 100644 index 0000000..0e5142b --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePassEnvironOptionValue.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.* + +/** + * Validator for PassEnvironment=. + * + * C function: config_parse_pass_environ in src/core/load-fragment.c. Whitespace-separated list + * of environment variable names. Each word is first run through unit_env_printf (resolves + * %p / %n / etc.), then env_name_is_valid: must not be empty, must not start with a digit, + * and may contain only characters in VALID_BASH_ENV_NAME_CHARS (alphanumerics and underscore). + * + * The grammar tolerates "%X" specifiers inline by including "%" in the allowed-character set, + * since the C parser resolves them before the validity check. This means a few unresolved + * specifier-heavy inputs that the runtime parser would reject will pass here — preferred over + * false positives for legitimate `PassEnvironment=%n_LOG` style usage. + */ +class ConfigParsePassEnvironOptionValue : SimpleGrammarOptionValues( + "config_parse_pass_environ", + SequenceCombinator( + RegexTerminal("[A-Za-z_%][A-Za-z0-9_%]*", "[A-Za-z_%][A-Za-z0-9_%]*"), + ZeroOrMore(SequenceCombinator( + WhitespaceTerminal(), + RegexTerminal("[A-Za-z_%][A-Za-z0-9_%]*", "[A-Za-z_%][A-Za-z0-9_%]*") + )), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSyscallErrnoOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSyscallErrnoOptionValue.kt new file mode 100644 index 0000000..ebde9b3 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSyscallErrnoOptionValue.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.* + +/** + * Validator for SystemCallErrorNumber=. + * + * C function: config_parse_syscall_errno in src/core/load-fragment.c → parse_errno in + * src/basic/parse-util.c. Accepts: + * - "kill" (special, resets to SECCOMP_ERROR_NUMBER_KILL) + * - an errno name (uppercase "E"-prefixed, e.g. EPERM, ENOENT) via errno_from_name + * - an integer that maps to a valid errno via errno_is_valid (or 0) + * + * The grammar matches the errno-name pattern loosely (any uppercase E-prefixed token); + * unknown names like "ENOIENT" would slip past the grammar but fail at runtime. Numeric + * range here is 0..4095 — wider than typical valid errnos but enough to catch obvious typos. + */ +class ConfigParseSyscallErrnoOptionValue : SimpleGrammarOptionValues( + "config_parse_syscall_errno", + SequenceCombinator( + AlternativeCombinator( + LiteralChoiceTerminal("kill"), + RegexTerminal("E[A-Z][A-Z0-9]*", "E[A-Z][A-Z0-9]*"), + IntegerTerminal(0, 4096) + ), + EOF() + ) +) diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseAddressFamiliesOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseAddressFamiliesOptionValueTest.kt new file mode 100644 index 0000000..8d37381 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseAddressFamiliesOptionValueTest.kt @@ -0,0 +1,48 @@ +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 ConfigParseAddressFamiliesOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Service] + RestrictAddressFamilies=none + RestrictAddressFamilies=AF_INET + RestrictAddressFamilies=AF_INET AF_INET6 + RestrictAddressFamilies=AF_UNIX AF_NETLINK + RestrictAddressFamilies=~AF_PACKET + RestrictAddressFamilies=~AF_INET AF_INET6 + RestrictAddressFamilies=AF_BRIDGE AF_X25 AF_AX25 + """.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 = """ + [Service] + RestrictAddressFamilies=inet + RestrictAddressFamilies=AF_inet + RestrictAddressFamilies=AF_INET, AF_INET6 + RestrictAddressFamilies=~ AF_PACKET + RestrictAddressFamilies=NONE + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCpuQuotaOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCpuQuotaOptionValueTest.kt new file mode 100644 index 0000000..fab7c8c --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCpuQuotaOptionValueTest.kt @@ -0,0 +1,49 @@ +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 ConfigParseCpuQuotaOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Service] + CPUQuota=50% + CPUQuota=100% + CPUQuota=200% + CPUQuota=12.34% + CPUQuota=1.5% + CPUQuota=0.01% + CPUQuota=99.99% + CPUQuota=1000% + """.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 = """ + [Service] + CPUQuota=50 + CPUQuota=abc + CPUQuota=-5% + CPUQuota=12.345% + CPUQuota=50% 60% + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDelegateOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDelegateOptionValueTest.kt new file mode 100644 index 0000000..74f18a0 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDelegateOptionValueTest.kt @@ -0,0 +1,49 @@ +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 ConfigParseDelegateOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Service] + Delegate=yes + Delegate=no + Delegate=true + Delegate=false + Delegate=cpu + Delegate=cpu memory io + Delegate=cpu cpuacct cpuset io blkio memory devices pids + Delegate=bpf-firewall bpf-devices bpf-foreign + Delegate=bpf-socket-bind bpf-restrict-network-interfaces bpf-bind-network-interface + """.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 = """ + [Service] + Delegate=maybe + Delegate=cpu unknown + Delegate=CPU + Delegate=cpu,memory + """.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/ConfigParseHtbClassSizeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassSizeOptionValueTest.kt new file mode 100644 index 0000000..b07c523 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassSizeOptionValueTest.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 ConfigParseHtbClassSizeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [HierarchyTokenBucketClass] + QuantumBytes=1500 + MTUBytes=1500 + OverheadBytes=64 + BufferBytes=8K + CeilBufferBytes=16K + BufferBytes=1M + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [HierarchyTokenBucketClass] + QuantumBytes=abc + MTUBytes=-1 + BufferBytes=8X + """.trimIndent() + + setupFileInEditor("file.network", 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/ConfigParsePassEnvironOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePassEnvironOptionValueTest.kt new file mode 100644 index 0000000..823d1c1 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePassEnvironOptionValueTest.kt @@ -0,0 +1,48 @@ +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 ConfigParsePassEnvironOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Service] + PassEnvironment=PATH + PassEnvironment=HOME LANG TERM + PassEnvironment=MY_VAR + PassEnvironment=_LEADING_UNDERSCORE + PassEnvironment=A B C D E + PassEnvironment=%n_LOG_LEVEL + PassEnvironment=PREFIX_%i + """.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 = """ + [Service] + PassEnvironment=1STARTS_WITH_DIGIT + PassEnvironment=HAS-DASH + PassEnvironment=HAS.DOT + PassEnvironment=NAME=value + PassEnvironment=PATH,HOME + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseSyscallErrnoOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseSyscallErrnoOptionValueTest.kt new file mode 100644 index 0000000..dd93c81 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseSyscallErrnoOptionValueTest.kt @@ -0,0 +1,50 @@ +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 ConfigParseSyscallErrnoOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Service] + SystemCallErrorNumber=kill + SystemCallErrorNumber=EPERM + SystemCallErrorNumber=ENOENT + SystemCallErrorNumber=EACCES + SystemCallErrorNumber=EAGAIN + SystemCallErrorNumber=1 + SystemCallErrorNumber=13 + SystemCallErrorNumber=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 = """ + [Service] + SystemCallErrorNumber=KILL + SystemCallErrorNumber=eperm + SystemCallErrorNumber=PERM + SystemCallErrorNumber=-1 + SystemCallErrorNumber=99999 + SystemCallErrorNumber=EPERM ENOENT + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } +} From f499f0bd755ea7d3fc406c1b67b3f994824fffcc Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Sat, 23 May 2026 10:43:59 -0700 Subject: [PATCH 2/3] feat: add 6 validators - private_users, managed_oom_rules, restrict_network_interfaces, bind_network_interface, working_directory, unit_env_file (Resolves #448) Batch 3/5 of the burn-down extension. Each grammar mirrors the systemd C parser: - config_parse_private_users (.nspawn): boolean | "pick" | "identity" | uid[:range] - config_parse_managed_oom_rules: list of filename-safe rule names (string_is_safe STRING_FILENAME) - config_parse_restrict_network_interfaces: optional ~ + list of ALTERNATIVE-length ifnames - config_parse_bind_network_interface: single ALTERNATIVE-length ifname (specifier-aware) - config_parse_working_directory: optional - + (~ | absolute path) - config_parse_unit_env_file: optional - + absolute path Alternative-order gotcha (from #447): for private_users, BOOLEAN is placed LAST so its FlexibleLiteralChoice syntactic regex doesn't greedily match prefixes of "pick"/"identity" and short-circuit the AlternativeCombinator. OptionValueTest missing-function count drops 388 -> 382; found-key count rises 1812 -> 1846. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../semanticdata/optionvalues/AiGenerated.kt | 6 +++ ...figParseBindNetworkInterfaceOptionValue.kt | 23 ++++++++ .../ConfigParseManagedOomRulesOptionValue.kt | 32 ++++++++++++ .../ai/ConfigParsePrivateUsersOptionValue.kt | 40 ++++++++++++++ ...rseRestrictNetworkInterfacesOptionValue.kt | 36 +++++++++++++ .../ai/ConfigParseUnitEnvFileOptionValue.kt | 21 ++++++++ .../ConfigParseWorkingDirectoryOptionValue.kt | 27 ++++++++++ ...arseBindNetworkInterfaceOptionValueTest.kt | 45 ++++++++++++++++ ...nfigParseManagedOomRulesOptionValueTest.kt | 45 ++++++++++++++++ .../ConfigParsePrivateUsersOptionValueTest.kt | 52 +++++++++++++++++++ ...estrictNetworkInterfacesOptionValueTest.kt | 47 +++++++++++++++++ .../ConfigParseUnitEnvFileOptionValueTest.kt | 45 ++++++++++++++++ ...figParseWorkingDirectoryOptionValueTest.kt | 47 +++++++++++++++++ 13 files changed, 466 insertions(+) create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBindNetworkInterfaceOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseManagedOomRulesOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateUsersOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRestrictNetworkInterfacesOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitEnvFileOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWorkingDirectoryOptionValue.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBindNetworkInterfaceOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseManagedOomRulesOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePrivateUsersOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRestrictNetworkInterfacesOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitEnvFileOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWorkingDirectoryOptionValueTest.kt 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 20538c8..ca31a46 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 @@ -25,6 +25,7 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_bare_udp_iftype", "0") to ConfigParseBareUdpIftypeOptionValue() as OptionValueInformation, Validator("config_parse_batadv_gateway_mode", "0") to ConfigParseBatadvGatewayModeOptionValue() as OptionValueInformation, Validator("config_parse_batadv_routing_algorithm", "0") to ConfigParseBatadvRoutingAlgorithmOptionValue() as OptionValueInformation, + Validator("config_parse_bind_network_interface", "0") to ConfigParseBindNetworkInterfaceOptionValue() as OptionValueInformation, Validator("config_parse_bond_ad_select", "0") to ConfigParseBondAdSelectOptionValue() as OptionValueInformation, Validator("config_parse_bond_arp_all_targets", "0") to ConfigParseBondArpAllTargetsOptionValue() as OptionValueInformation, Validator("config_parse_bond_arp_validate", "0") to ConfigParseBondArpValidateOptionValue() as OptionValueInformation, @@ -130,6 +131,7 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_macvlan_mode", "0") to ConfigParseMacvlanModeOptionValue() as OptionValueInformation, Validator("config_parse_managed_oom_mem_pressure_duration_sec", "0") to ConfigParseManagedOomMemPressureDurationSecOptionValue() as OptionValueInformation, Validator("config_parse_managed_oom_mem_pressure_limit", "0") to ConfigParseManagedOomMemPressureLimitOptionValue() as OptionValueInformation, + Validator("config_parse_managed_oom_rules", "1") to ConfigParseManagedOomRulesOptionValue() as OptionValueInformation, Validator("config_parse_mdi", "0") to ConfigParseMdiOptionValue() as OptionValueInformation, Validator("config_parse_pressure_watch", "0") to ConfigParseMemoryPressureWatchOptionValue() as OptionValueInformation, Validator("config_parse_mtu", "AF_INET6") to ConfigParseMtuOptionValue() as OptionValueInformation, @@ -155,10 +157,12 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_prefix_metric", "0") to ConfigParsePrefixMetricOptionValue() as OptionValueInformation, Validator("config_parse_private_pids", "0") to ConfigParsePrivatePidsOptionValue() as OptionValueInformation, Validator("config_parse_private_tmp", "0") to ConfigParsePrivateTmpOptionValue() as OptionValueInformation, + Validator("config_parse_private_users", "0") to ConfigParsePrivateUsersOptionValue() as OptionValueInformation, Validator("config_parse_protect_control_groups", "0") to ConfigParseProtectControlGroupsOptionValue() as OptionValueInformation, Validator("config_parse_protect_home", "0") to ConfigParseProtectHomeOptionValue() as OptionValueInformation, Validator("config_parse_protect_system", "0") to ConfigParseProtectSystemOptionValue() as OptionValueInformation, Validator("config_parse_qfq_weight", "TCLASS_KIND_QFQ") to ConfigParseQfqWeightOptionValue() as OptionValueInformation, + Validator("config_parse_restrict_network_interfaces", "0") to ConfigParseRestrictNetworkInterfacesOptionValue() as OptionValueInformation, Validator("config_parse_ring_buffer_or_channel", "0") to ConfigParseRingBufferOrChannelOptionValue() as OptionValueInformation, Validator("config_parse_route_prefix_preference", "0") to ConfigParseRoutePrefixPreferenceOptionValue() as OptionValueInformation, Validator("config_parse_route_section", "ROUTE_METRIC_FASTOPEN_NO_COOKIE") to ConfigParseRouteSectionOptionValue() as OptionValueInformation, @@ -192,12 +196,14 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_unit_condition_string", "CONDITION_CONTROL_GROUP_CONTROLLER") to ConfigParseUnitConditionStringOptionValue() as OptionValueInformation, Validator("config_parse_unit_condition_string", "CONDITION_CPU_FEATURE") to ConfigParseUnitConditionStringOptionValue() as OptionValueInformation, Validator("config_parse_unit_condition_string", "CONDITION_FIRST_BOOT") to ConfigParseUnitConditionStringOptionValue() as OptionValueInformation, + Validator("config_parse_unit_env_file", "0") to ConfigParseUnitEnvFileOptionValue() as OptionValueInformation, Validator("config_parse_unit_slice", "0") to ConfigParseUnitSliceOptionValue() as OptionValueInformation, Validator("config_parse_use_domains", "0") to ConfigParseUseDomainsOptionValue() as OptionValueInformation, Validator("config_parse_userns_chown", "0") to ConfigParseUsernsChownOptionValue() as OptionValueInformation, Validator("config_parse_userns_ownership", "0") to ConfigParseUsernsOwnershipOptionValue() as OptionValueInformation, Validator("config_parse_vlanid", "0") to ConfigParseVlanidOptionValue() as OptionValueInformation, Validator("config_parse_vxlan_ttl", "0") to ConfigParseVxlanTtlOptionValue() as OptionValueInformation, + Validator("config_parse_working_directory", "0") to ConfigParseWorkingDirectoryOptionValue() as OptionValueInformation, Validator("config_parse_wireguard_keepalive", "0") to ConfigParseWireguardKeepaliveOptionValue() as OptionValueInformation, Validator("config_parse_wireguard_listen_port", "0") to ConfigParseWireguardListenPortOptionValue() as OptionValueInformation, Validator("config_parse_wireguard_peer_route_priority", "0") to ConfigParseWireguardPeerRoutePriorityOptionValue() as OptionValueInformation, diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBindNetworkInterfaceOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBindNetworkInterfaceOptionValue.kt new file mode 100644 index 0000000..e1f92c5 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBindNetworkInterfaceOptionValue.kt @@ -0,0 +1,23 @@ +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.* + +/** + * Validator for BindNetworkInterface=. + * + * C function: config_parse_bind_network_interface in src/core/load-fragment.c. After + * unit_full_printf specifier expansion, the result must pass ifname_valid_full( + * IFNAME_VALID_ALTERNATIVE) — same rules as RestrictNetworkInterfaces= entries but the value + * is a single interface name (no whitespace-separated list, no leading "~"). + */ +class ConfigParseBindNetworkInterfaceOptionValue : SimpleGrammarOptionValues( + "config_parse_bind_network_interface", + SequenceCombinator( + RegexTerminal( + "(?!(?:all|default|\\.{1,2}|0[xX][0-9a-fA-F]+|[0-9]+)\\Z)[^\\s:/%]{1,127}", + "(?!(?:all|default|\\.{1,2}|0[xX][0-9a-fA-F]+|[0-9]+)\\Z)[^\\s:/%]{1,127}" + ), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseManagedOomRulesOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseManagedOomRulesOptionValue.kt new file mode 100644 index 0000000..997c72e --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseManagedOomRulesOptionValue.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.* + +/** + * Validator for ManagedOOMRulesets= and friends. + * + * C function: config_parse_managed_oom_rules in src/core/load-fragment.c. After an + * unsupported-unit-type check it tokenizes rvalue with extract_first_word and accepts each + * token that passes string_is_safe(STRING_FILENAME) — i.e. filename-safe characters, not + * "." / "..", no slashes, no whitespace, no shell metacharacters. The rules themselves are + * loaded from .oomrule files at runtime so the grammar only validates the per-token shape. + */ +class ConfigParseManagedOomRulesOptionValue : SimpleGrammarOptionValues( + "config_parse_managed_oom_rules", + SequenceCombinator( + RULE_NAME, + ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), RULE_NAME)), + EOF() + ) +) { + companion object { + // STRING_FILENAME forbids slashes, NUL, whitespace, and shell metacharacters. The + // regex below is a conservative ASCII printable set excluding those plus the "." / + // ".." filename reservations (rejected via the negative lookahead). + private val RULE_NAME = RegexTerminal( + "(?!\\.{1,2}\\Z)[A-Za-z0-9._-][A-Za-z0-9._+-]*", + "(?!\\.{1,2}\\Z)[A-Za-z0-9._-][A-Za-z0-9._+-]*" + ) + } +} diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateUsersOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateUsersOptionValue.kt new file mode 100644 index 0000000..867d0b4 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateUsersOptionValue.kt @@ -0,0 +1,40 @@ +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.* + +/** + * Validator for the .nspawn PrivateUsers= setting. + * + * C function: config_parse_private_users in src/nspawn/nspawn-settings.c. Accepts (in order + * the C code checks): + * - boolean ("yes" / "no" / "1" / "0" / "true" / "false" / "on" / "off") + * - "pick" (random UID shift) + * - "identity" (UID shift 0, range 64K) + * - `uid` (single uint32, range defaults to 64K) + * - `uid:range` (two uint32 separated by colon) + * + * Alternatives are ordered from most-specific to least-specific so the colon form is tried + * before the bare uid, and BOOLEAN (FlexibleLiteralChoiceTerminal) comes last — its syntactic + * regex would otherwise greedily match tokens like "pick" or "identity" as boolean prefixes + * and short-circuit AlternativeCombinator. + */ +class ConfigParsePrivateUsersOptionValue : SimpleGrammarOptionValues( + "config_parse_private_users", + SequenceCombinator( + AlternativeCombinator( + // uid:range + SequenceCombinator( + IntegerTerminal(0, 4_294_967_296L), + LiteralChoiceTerminal(":"), + IntegerTerminal(0, 4_294_967_296L) + ), + // bare uid + IntegerTerminal(0, 4_294_967_296L), + // string aliases + LiteralChoiceTerminal("pick", "identity"), + BOOLEAN + ), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRestrictNetworkInterfacesOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRestrictNetworkInterfacesOptionValue.kt new file mode 100644 index 0000000..b879a64 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRestrictNetworkInterfacesOptionValue.kt @@ -0,0 +1,36 @@ +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.* + +/** + * Validator for RestrictNetworkInterfaces=. + * + * C function: config_parse_restrict_network_interfaces in src/core/load-fragment.c. Optional + * leading "~" (invert / denylist mode), followed by a whitespace-separated list of interface + * names that must pass ifname_valid_full(IFNAME_VALID_ALTERNATIVE): + * - 1..127 characters (ALTIFNAMSIZ - 1) + * - no whitespace, no '/', no ':', no '%' (rejected by ifname_valid_char) + * - cannot be "." or ".." + * - cannot be "all" or "default" + * - cannot be a purely-numeric string (interpreted as ifindex) + * + * The regex mirrors the existing config_parse_ifname validator but extends the length cap + * from 15 (IFNAMSIZ) to 127 (ALTIFNAMSIZ). + */ +class ConfigParseRestrictNetworkInterfacesOptionValue : SimpleGrammarOptionValues( + "config_parse_restrict_network_interfaces", + SequenceCombinator( + ZeroOrOne(LiteralChoiceTerminal("~")), + IFNAME, + ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), IFNAME)), + EOF() + ) +) { + companion object { + private val IFNAME = RegexTerminal( + "(?!(?:all|default|\\.{1,2}|0[xX][0-9a-fA-F]+|[0-9]+)\\Z)[^\\s:/%]{1,127}", + "(?!(?:all|default|\\.{1,2}|0[xX][0-9a-fA-F]+|[0-9]+)\\Z)[^\\s:/%]{1,127}" + ) + } +} diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitEnvFileOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitEnvFileOptionValue.kt new file mode 100644 index 0000000..0a13f71 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitEnvFileOptionValue.kt @@ -0,0 +1,21 @@ +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.* + +/** + * Validator for EnvironmentFile=. + * + * C function: config_parse_unit_env_file in src/core/load-fragment.c. Each line is a single + * environment file path (after unit_path_printf specifier expansion); an optional leading "-" + * marks the file as missing-OK. The resolved path must be absolute (path_simplify_and_warn + * with PATH_CHECK_ABSOLUTE). + */ +class ConfigParseUnitEnvFileOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_env_file", + SequenceCombinator( + ZeroOrOne(LiteralChoiceTerminal("-")), + RegexTerminal("/\\S*", "/\\S*"), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWorkingDirectoryOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWorkingDirectoryOptionValue.kt new file mode 100644 index 0000000..13b725b --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWorkingDirectoryOptionValue.kt @@ -0,0 +1,27 @@ +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.* + +/** + * Validator for WorkingDirectory=. + * + * C function: config_parse_working_directory in src/core/load-fragment.c. Accepts: + * - optional leading "-" (missing path is non-fatal) + * - then either "~" alone (use the user's home) OR an absolute path + * + * Path values go through unit_path_printf so "%X" specifiers may appear inline. The grammar + * allows any non-whitespace characters in the path; tighter shell-metacharacter checks happen + * in path_simplify_and_warn at runtime. + */ +class ConfigParseWorkingDirectoryOptionValue : SimpleGrammarOptionValues( + "config_parse_working_directory", + SequenceCombinator( + ZeroOrOne(LiteralChoiceTerminal("-")), + AlternativeCombinator( + LiteralChoiceTerminal("~"), + RegexTerminal("/\\S*", "/\\S*") + ), + EOF() + ) +) diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBindNetworkInterfaceOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBindNetworkInterfaceOptionValueTest.kt new file mode 100644 index 0000000..f29206b --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBindNetworkInterfaceOptionValueTest.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 ConfigParseBindNetworkInterfaceOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Service] + BindNetworkInterface=eth0 + BindNetworkInterface=lo + BindNetworkInterface=wlan0 + BindNetworkInterface=br-1234abcd + """.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 = """ + [Service] + BindNetworkInterface=all + BindNetworkInterface=default + BindNetworkInterface=eth0 eth1 + BindNetworkInterface=eth0/0 + BindNetworkInterface=123 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseManagedOomRulesOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseManagedOomRulesOptionValueTest.kt new file mode 100644 index 0000000..0047a92 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseManagedOomRulesOptionValueTest.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 ConfigParseManagedOomRulesOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Service] + OOMRules=default + OOMRules=default critical + OOMRules=rule1 rule2 rule3 + OOMRules=my-rule_42 + OOMRules=rule.with.dots + """.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 = """ + [Service] + OOMRules=rule/with/slash + OOMRules=. + OOMRules=.. + OOMRules=rule,other + """.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/ConfigParsePrivateUsersOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePrivateUsersOptionValueTest.kt new file mode 100644 index 0000000..d4c30f7 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePrivateUsersOptionValueTest.kt @@ -0,0 +1,52 @@ +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 ConfigParsePrivateUsersOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Exec] + PrivateUsers=yes + PrivateUsers=no + PrivateUsers=true + PrivateUsers=false + PrivateUsers=pick + PrivateUsers=identity + PrivateUsers=1000 + PrivateUsers=0 + PrivateUsers=1000:65536 + PrivateUsers=100000:1000000 + """.trimIndent() + + setupFileInEditor("file.nspawn", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Exec] + PrivateUsers=maybe + PrivateUsers=Pick + PrivateUsers=-1 + PrivateUsers=1000: + PrivateUsers=:1000 + PrivateUsers=1000 65536 + """.trimIndent() + + setupFileInEditor("file.nspawn", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRestrictNetworkInterfacesOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRestrictNetworkInterfacesOptionValueTest.kt new file mode 100644 index 0000000..a853131 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRestrictNetworkInterfacesOptionValueTest.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 ConfigParseRestrictNetworkInterfacesOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Service] + RestrictNetworkInterfaces=eth0 + RestrictNetworkInterfaces=eth0 eth1 + RestrictNetworkInterfaces=lo eth0 wlan0 + RestrictNetworkInterfaces=~eth0 + RestrictNetworkInterfaces=~lo eth0 + RestrictNetworkInterfaces=br-1234abcd + """.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 = """ + [Service] + RestrictNetworkInterfaces=all + RestrictNetworkInterfaces=default + RestrictNetworkInterfaces=eth0/0 + RestrictNetworkInterfaces=eth0:0 + RestrictNetworkInterfaces=123 + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitEnvFileOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitEnvFileOptionValueTest.kt new file mode 100644 index 0000000..1a640ac --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitEnvFileOptionValueTest.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 ConfigParseUnitEnvFileOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Service] + EnvironmentFile=/etc/myapp.env + EnvironmentFile=-/etc/myapp/optional.env + EnvironmentFile=/etc/sysconfig/myapp + EnvironmentFile=/var/lib/%n.env + EnvironmentFile=-/run/secrets/myapp.env + """.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 = """ + [Service] + EnvironmentFile=relative/path.env + EnvironmentFile=myapp.env + EnvironmentFile=~/myapp.env + EnvironmentFile=--/etc/myapp.env + """.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/ConfigParseWorkingDirectoryOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWorkingDirectoryOptionValueTest.kt new file mode 100644 index 0000000..8e39b9b --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWorkingDirectoryOptionValueTest.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 ConfigParseWorkingDirectoryOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Service] + WorkingDirectory=~ + WorkingDirectory=/var/lib/myapp + WorkingDirectory=/tmp + WorkingDirectory=/ + WorkingDirectory=-/var/cache/myapp + WorkingDirectory=-~ + WorkingDirectory=/var/lib/%n + """.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 = """ + [Service] + WorkingDirectory=relative/path + WorkingDirectory=somepath + WorkingDirectory=/path with space + WorkingDirectory=--/path + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(4, highlights) + } +} From 7b15e4b51f940a7043e9bf07c05e49f7e84ee4f2 Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Sat, 23 May 2026 11:22:03 -0700 Subject: [PATCH 3/3] feat: add 6 validators - iec_size, fq_codel_size, int, job_mode, reboot_parameter, unit_mounts_for (Resolves #450) (#451) Batch 4/5. Each grammar mirrors the systemd C parser: - config_parse_iec_size: parse_size IEC byte (existing BYTES grammar) - config_parse_fq_codel_size (QDISC_KIND_FQ_CODEL): same parse_size shape - config_parse_int: signed 32-bit integer (DEFINE_PARSER + safe_atoi) - config_parse_job_mode: enum from job_mode_table (10 entries) - config_parse_reboot_parameter: printable ASCII length 1..255 - config_parse_unit_mounts_for: whitespace-separated absolute paths (specifier-aware) OptionValueTest missing-function count drops 382 -> 376; found-key count rises 1846 -> 1870. Co-authored-by: Steve Ramage Co-authored-by: Claude Opus 4.7 (1M context) --- .../semanticdata/optionvalues/AiGenerated.kt | 6 +++ .../ai/ConfigParseFqCodelSizeOptionValue.kt | 20 ++++++++ .../ai/ConfigParseIecSizeOptionValue.kt | 19 +++++++ .../ai/ConfigParseIntOptionValue.kt | 18 +++++++ .../ai/ConfigParseJobModeOptionValue.kt | 30 +++++++++++ .../ConfigParseRebootParameterOptionValue.kt | 23 +++++++++ .../ai/ConfigParseUnitMountsForOptionValue.kt | 27 ++++++++++ .../ConfigParseFqCodelSizeOptionValueTest.kt | 43 ++++++++++++++++ .../ai/ConfigParseIecSizeOptionValueTest.kt | 43 ++++++++++++++++ .../ai/ConfigParseIntOptionValueTest.kt | 45 +++++++++++++++++ .../ai/ConfigParseJobModeOptionValueTest.kt | 50 +++++++++++++++++++ ...nfigParseRebootParameterOptionValueTest.kt | 42 ++++++++++++++++ ...ConfigParseUnitMountsForOptionValueTest.kt | 43 ++++++++++++++++ 13 files changed, 409 insertions(+) create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelSizeOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIecSizeOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIntOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseJobModeOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRebootParameterOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitMountsForOptionValue.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelSizeOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIecSizeOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIntOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseJobModeOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRebootParameterOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitMountsForOptionValueTest.kt 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 ca31a46..e2ed319 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 @@ -94,6 +94,7 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_flow_label", "0") to ConfigParseFlowLabelOptionValue() as OptionValueInformation, Validator("config_parse_fou_encap_type", "0") to ConfigParseFouEncapTypeOptionValue() as OptionValueInformation, Validator("config_parse_fou_tunnel_address", "0") to ConfigParseFouTunnelAddressOptionValue() as OptionValueInformation, + Validator("config_parse_fq_codel_size", "QDISC_KIND_FQ_CODEL") to ConfigParseFqCodelSizeOptionValue() as OptionValueInformation, Validator("config_parse_fq_pie_packet_limit", "QDISC_KIND_FQ_PIE") to ConfigParseFqPiePacketLimitOptionValue() as OptionValueInformation, Validator("config_parse_geneve_df", "0") to ConfigParseGeneveDfOptionValue() as OptionValueInformation, Validator("config_parse_geneve_flow_label", "0") to ConfigParseGeneveFlowLabelOptionValue() as OptionValueInformation, @@ -103,6 +104,8 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_htb_class_size", "TCLASS_KIND_HTB") to ConfigParseHtbClassSizeOptionValue() as OptionValueInformation, Validator("config_parse_iaid", "AF_INET6") to ConfigParseIaidOptionValue() as OptionValueInformation, Validator("config_parse_iaid", "AF_INET") to ConfigParseIaidOptionValue() as OptionValueInformation, + Validator("config_parse_iec_size", "0") to ConfigParseIecSizeOptionValue() as OptionValueInformation, + Validator("config_parse_int", "0") to ConfigParseIntOptionValue() as OptionValueInformation, Validator("config_parse_in_addr_non_null", "AF_INET") to ConfigParseInAddrNonNullOptionValue() as OptionValueInformation, Validator("config_parse_ip_masquerade", "0") to ConfigParseIpMasqueradeOptionValue() as OptionValueInformation, Validator("config_parse_ip_protocol", "true") to ConfigParseIpProtocolOptionValue() as OptionValueInformation, @@ -113,6 +116,7 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_ipv6_privacy_extensions", "0") to ConfigParseIpv6PrivacyExtensionsOptionValue() as OptionValueInformation, Validator("config_parse_ipvlan_flags", "0") to ConfigParseIpvlanFlagsOptionValue() as OptionValueInformation, Validator("config_parse_ipvlan_mode", "0") to ConfigParseIpvlanModeOptionValue() as OptionValueInformation, + Validator("config_parse_job_mode", "0") to ConfigParseJobModeOptionValue() as OptionValueInformation, Validator("config_parse_job_mode_isolate", "0") to ConfigParseJobModeIsolateOptionValue() as OptionValueInformation, Validator("config_parse_keep_configuration", "0") to ConfigParseKeepConfigurationOptionValue() as OptionValueInformation, Validator("config_parse_l2tp_encap_type", "0") to ConfigParseL2tpEncapTypeOptionValue() as OptionValueInformation, @@ -162,6 +166,7 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_protect_home", "0") to ConfigParseProtectHomeOptionValue() as OptionValueInformation, Validator("config_parse_protect_system", "0") to ConfigParseProtectSystemOptionValue() as OptionValueInformation, Validator("config_parse_qfq_weight", "TCLASS_KIND_QFQ") to ConfigParseQfqWeightOptionValue() as OptionValueInformation, + Validator("config_parse_reboot_parameter", "0") to ConfigParseRebootParameterOptionValue() as OptionValueInformation, Validator("config_parse_restrict_network_interfaces", "0") to ConfigParseRestrictNetworkInterfacesOptionValue() as OptionValueInformation, Validator("config_parse_ring_buffer_or_channel", "0") to ConfigParseRingBufferOrChannelOptionValue() as OptionValueInformation, Validator("config_parse_route_prefix_preference", "0") to ConfigParseRoutePrefixPreferenceOptionValue() as OptionValueInformation, @@ -197,6 +202,7 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_unit_condition_string", "CONDITION_CPU_FEATURE") to ConfigParseUnitConditionStringOptionValue() as OptionValueInformation, Validator("config_parse_unit_condition_string", "CONDITION_FIRST_BOOT") to ConfigParseUnitConditionStringOptionValue() as OptionValueInformation, Validator("config_parse_unit_env_file", "0") to ConfigParseUnitEnvFileOptionValue() as OptionValueInformation, + Validator("config_parse_unit_mounts_for", "0") to ConfigParseUnitMountsForOptionValue() as OptionValueInformation, Validator("config_parse_unit_slice", "0") to ConfigParseUnitSliceOptionValue() as OptionValueInformation, Validator("config_parse_use_domains", "0") to ConfigParseUseDomainsOptionValue() as OptionValueInformation, Validator("config_parse_userns_chown", "0") to ConfigParseUsernsChownOptionValue() as OptionValueInformation, diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelSizeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelSizeOptionValue.kt new file mode 100644 index 0000000..19add4d --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelSizeOptionValue.kt @@ -0,0 +1,20 @@ +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.* + +/** + * Validator for the [FairQueueingControlledDelay] size-style options: MemoryLimitBytes=, + * QuantumBytes= (and the deprecated MemoryLimit / Quantum aliases). + * + * C function: config_parse_fq_codel_size(QDISC_KIND_FQ_CODEL) in src/network/tc/fq-codel.c. + * After branching on the lvalue it calls parse_size(rvalue, 1024, &sz). Same IEC byte shape + * as the other parse_size-based validators. + */ +class ConfigParseFqCodelSizeOptionValue : SimpleGrammarOptionValues( + "config_parse_fq_codel_size", + SequenceCombinator( + OptionalWhitespacePrefix(BYTES), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIecSizeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIecSizeOptionValue.kt new file mode 100644 index 0000000..7739bc4 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIecSizeOptionValue.kt @@ -0,0 +1,19 @@ +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.* + +/** + * Validator for generic IEC byte-size options parsed by config_parse_iec_size. + * + * C function: config_parse_iec_size in src/shared/conf-parser.c. Delegates to + * parse_size(rvalue, 1024, &v) and additionally requires the result to fit in a size_t. + * Same IEC byte syntax used by tbf_size / fq_size / htb_class_size. + */ +class ConfigParseIecSizeOptionValue : SimpleGrammarOptionValues( + "config_parse_iec_size", + SequenceCombinator( + OptionalWhitespacePrefix(BYTES), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIntOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIntOptionValue.kt new file mode 100644 index 0000000..78db652 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIntOptionValue.kt @@ -0,0 +1,18 @@ +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.* + +/** + * Validator for generic signed-integer options parsed by config_parse_int. + * + * C function: defined via DEFINE_PARSER(int, int, safe_atoi) in src/shared/conf-parser.c. + * Accepts a signed 32-bit integer. + */ +class ConfigParseIntOptionValue : SimpleGrammarOptionValues( + "config_parse_int", + SequenceCombinator( + IntegerTerminal(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong() + 1L), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseJobModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseJobModeOptionValue.kt new file mode 100644 index 0000000..19a5037 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseJobModeOptionValue.kt @@ -0,0 +1,30 @@ +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.* + +/** + * Validator for job-mode options (e.g. OnFailureJobMode=, OnSuccessJobMode=). + * + * C function: config_parse_job_mode, expanded via DEFINE_CONFIG_PARSE_ENUM in + * src/core/load-fragment.c. Accepts exactly the entries of job_mode_table in + * src/basic/unit-def.c. + */ +class ConfigParseJobModeOptionValue : SimpleGrammarOptionValues( + "config_parse_job_mode", + SequenceCombinator( + LiteralChoiceTerminal( + "fail", + "lenient", + "replace", + "replace-irreversibly", + "isolate", + "flush", + "ignore-dependencies", + "ignore-requirements", + "triggering", + "restart-dependencies" + ), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRebootParameterOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRebootParameterOptionValue.kt new file mode 100644 index 0000000..c5e23d4 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRebootParameterOptionValue.kt @@ -0,0 +1,23 @@ +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.* + +/** + * Validator for RebootArgument= / RebootParameter= keys. + * + * C function: config_parse_reboot_parameter in src/core/load-fragment.c → reboot_parameter_is_valid + * in src/shared/reboot-util.c. After unit_full_printf specifier expansion, the result must: + * - pass ascii_is_valid (printable ASCII) + * - have length ≤ NAME_MAX (255) + * + * Grammar allows printable ASCII (0x20..0x7E) including "%" for specifier syntax; length cap + * of 255 is enforced via a regex quantifier. + */ +class ConfigParseRebootParameterOptionValue : SimpleGrammarOptionValues( + "config_parse_reboot_parameter", + SequenceCombinator( + RegexTerminal("[\\x20-\\x7E]{1,255}", "[\\x20-\\x7E]{1,255}"), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitMountsForOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitMountsForOptionValue.kt new file mode 100644 index 0000000..c4450a0 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitMountsForOptionValue.kt @@ -0,0 +1,27 @@ +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.* + +/** + * Validator for RequiresMountsFor= / WantsMountsFor=. + * + * C function: config_parse_unit_mounts_for in src/core/load-fragment.c. Tokenizes rvalue with + * extract_first_word (whitespace-separated, EXTRACT_UNQUOTE), expands unit specifiers via + * unit_path_printf, then requires each path to be absolute via path_simplify_and_warn with + * PATH_CHECK_ABSOLUTE. + * + * The grammar allows any non-whitespace path that starts with "/", and accepts "%X" specifiers + * inline since the C parser expands them before the absolute-path check. + */ +class ConfigParseUnitMountsForOptionValue : SimpleGrammarOptionValues( + "config_parse_unit_mounts_for", + SequenceCombinator( + RegexTerminal("/\\S*", "/\\S*"), + ZeroOrMore(SequenceCombinator( + WhitespaceTerminal(), + RegexTerminal("/\\S*", "/\\S*") + )), + EOF() + ) +) diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelSizeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelSizeOptionValueTest.kt new file mode 100644 index 0000000..30b4a73 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelSizeOptionValueTest.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 ConfigParseFqCodelSizeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [FairQueueingControlledDelay] + MemoryLimitBytes=32M + QuantumBytes=1500 + MemoryLimit=64M + Quantum=2K + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [FairQueueingControlledDelay] + MemoryLimitBytes=abc + QuantumBytes=-1 + MemoryLimit=10X + """.trimIndent() + + setupFileInEditor("file.network", 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/ConfigParseIecSizeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIecSizeOptionValueTest.kt new file mode 100644 index 0000000..81e9ac3 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIecSizeOptionValueTest.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 ConfigParseIecSizeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Socket] + ReceiveBuffer=4096 + SendBuffer=64K + PipeSize=1M + ReceiveBuffer=2G + """.trimIndent() + + setupFileInEditor("file.socket", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Socket] + ReceiveBuffer=abc + SendBuffer=-1 + PipeSize=10X + """.trimIndent() + + setupFileInEditor("file.socket", 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/ConfigParseIntOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIntOptionValueTest.kt new file mode 100644 index 0000000..f94d089 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIntOptionValueTest.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 ConfigParseIntOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Socket] + Priority=0 + IPTTL=64 + Mark=-1 + Priority=2147483647 + IPTTL=-2147483648 + """.trimIndent() + + setupFileInEditor("file.socket", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Socket] + Priority=abc + IPTTL=1.5 + Mark=2147483648 + Priority=10 20 + """.trimIndent() + + setupFileInEditor("file.socket", 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/ConfigParseJobModeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseJobModeOptionValueTest.kt new file mode 100644 index 0000000..034a58a --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseJobModeOptionValueTest.kt @@ -0,0 +1,50 @@ +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 ConfigParseJobModeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + OnSuccessJobMode=fail + OnFailureJobMode=replace + OnSuccessJobMode=replace-irreversibly + OnFailureJobMode=isolate + OnSuccessJobMode=flush + OnFailureJobMode=ignore-dependencies + OnSuccessJobMode=ignore-requirements + OnFailureJobMode=triggering + OnSuccessJobMode=restart-dependencies + OnFailureJobMode=lenient + """.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] + OnSuccessJobMode=unknown + OnFailureJobMode=Replace + OnSuccessJobMode=fail isolate + OnFailureJobMode=replace_irreversibly + """.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/ConfigParseRebootParameterOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRebootParameterOptionValueTest.kt new file mode 100644 index 0000000..1532273 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRebootParameterOptionValueTest.kt @@ -0,0 +1,42 @@ +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 ConfigParseRebootParameterOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + RebootArgument=recovery + JobTimeoutRebootArgument=panic + RebootArgument=bootloader + JobTimeoutRebootArgument=arg with spaces + """.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] + RebootArgument=café + JobTimeoutRebootArgument=日本 + """.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/ConfigParseUnitMountsForOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitMountsForOptionValueTest.kt new file mode 100644 index 0000000..d58eabe --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUnitMountsForOptionValueTest.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 ConfigParseUnitMountsForOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Unit] + RequiresMountsFor=/var/lib/myapp + WantsMountsFor=/tmp /var/cache + RequiresMountsFor=/srv /opt/app /home/user + WantsMountsFor=/var/log/%n + """.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] + RequiresMountsFor=relative/path + WantsMountsFor=no-leading-slash + RequiresMountsFor=/valid relative/path + """.trimIndent() + + setupFileInEditor("file.service", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +}