From 482c1e2aeaf9b88380a7863114ed949b13d84898 Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Sat, 24 Oct 2026 06:33:19 -0700 Subject: [PATCH 1/3] fix: accept case-insensitive booleans in grammar validators systemd's parse_boolean() compares with STRCASE_IN_SET, so values such as PrivateTmp=True or PrivateUsers=False are valid, but the shared BOOLEAN and BOOLEAN_FALSE terminals (and deprecatedBoolean) matched case-sensitively. They now pass ignoreCase = true; the lower-case spellings remain canonical for quick-fixes. A new BOOLEAN_TRUE terminal covers ProtectHostname=yes:HOST. Validators that inlined the twelve boolean spellings into one case-sensitive choice are restructured as names-first-then-boolean via a new namesOrBoolean() helper, mirroring string_table_lookup_from_string_with_boolean: only the boolean half ignores case, the table names stay case-sensitive (PrivateTmp=Disconnected is still rejected). Name lists now match the C tables at 0f0682b773 (e.g. PrivateTmp=connected, RuntimeDirectoryPreserve= on-success, Timezone=symlink/delete/auto, PrivateUsersOwnership=foreign/auto, DHCP= deprecated none/both/v4/v6), and MulticastRouter= accepts only false booleans because its table maps "yes" to _MULTICAST_ROUTER_INVALID. namesOrBoolean uses a new PreferValidAlternativeCombinator so that, under the classic engine, a lower-case names terminal's lenient shape match cannot strand a mixed-case boolean (the "t" of "tRUE") or a boolean longer than every name (the "fals" of DuplicateAddressDetection=false). CONDITION_VIRTUALIZATION, DuplicateAddressDetection=, DNSSEC= and RestrictNamespaces= use it too. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../ai/AddressAndNextHopFlagOptionValues.kt | 8 +- .../ai/ConfigParseCakeTristateOptionValue.kt | 13 +- .../ai/ConfigParseDfOptionValue.kt | 13 +- .../ai/ConfigParseDhcpOptionValue.kt | 25 ++- ...ParseDhcpServerPersistLeasesOptionValue.kt | 11 +- .../ai/ConfigParseDnssecModeOptionValue.kt | 10 +- .../ConfigParseExecPreserveModeOptionValue.kt | 18 +- .../ai/ConfigParseGeneveDfOptionValue.kt | 19 +- .../ai/ConfigParseIpMasqueradeOptionValue.kt | 39 ++-- ...igParseIpv6PrivacyExtensionsOptionValue.kt | 20 +- ...ConfigParseKeepConfigurationOptionValue.kt | 25 +-- ...gParseLinkLocalAddressFamilyOptionValue.kt | 16 +- .../ai/ConfigParseLldpModeOptionValue.kt | 16 +- ...nfigParseMemoryPressureWatchOptionValue.kt | 11 +- .../ConfigParseMulticastRouterOptionValue.kt | 12 +- .../ConfigParseNamespaceFlagsOptionValue.kt | 6 +- ...igParseNdiscStartDhcp6ClientOptionValue.kt | 13 +- .../ai/ConfigParsePrivateTmpOptionValue.kt | 20 +- ...figParseProtectControlGroupsOptionValue.kt | 19 +- .../ai/ConfigParseProtectHomeOptionValue.kt | 23 +-- .../ConfigParseProtectHostnameOptionValue.kt | 31 +-- .../ai/ConfigParseProtectSystemOptionValue.kt | 19 +- ...onfigParseSocketDeferTriggerOptionValue.kt | 11 +- .../ai/ConfigParseTimezoneModeOptionValue.kt | 13 +- .../ai/ConfigParseUseDomainsOptionValue.kt | 14 +- .../ConfigParseUsernsOwnershipOptionValue.kt | 36 +--- .../optionvalues/grammar/Combinators.kt | 46 ++++- .../PreferValidAlternativeCombinator.kt | 31 +++ .../CaseInsensitiveBooleanInspectionTest.kt | 185 ++++++++++++++++++ .../ai/ConfigParseDhcpOptionValueTest.kt | 20 +- ...figParseExecPreserveModeOptionValueTest.kt | 9 +- .../ConfigParseTimezoneModeOptionValueTest.kt | 10 +- ...nfigParseUsernsOwnershipOptionValueTest.kt | 7 +- 33 files changed, 496 insertions(+), 273 deletions(-) create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/PreferValidAlternativeCombinator.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/CaseInsensitiveBooleanInspectionTest.kt diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/AddressAndNextHopFlagOptionValues.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/AddressAndNextHopFlagOptionValues.kt index 00a700fb..cd4c66e6 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/AddressAndNextHopFlagOptionValues.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/AddressAndNextHopFlagOptionValues.kt @@ -1,7 +1,7 @@ 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.namesOrBoolean import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.deprecatedBoolean import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF @@ -75,8 +75,10 @@ class ConfigParseAddressSectionDadOptionValue : SimpleGrammarOptionValues( "yes means none and no means both. Please use 'both', 'ipv4', 'ipv6' or 'none' instead." // The family names come first: a boolean terminal matches a prefix of the value, so it would - // otherwise take the leading "no" out of "none" and strand "ne". - val DAD = AlternativeCombinator( + // otherwise take the leading "no" out of "none" and strand "ne". namesOrBoolean also stops the + // four-character names' lenient shape from taking "fals" out of "false". The family names are a + // case-sensitive table lookup; the boolean (parse_boolean) ignores case. + val DAD = namesOrBoolean( FlexibleLiteralChoiceTerminal("none", "both", "ipv4", "ipv6"), deprecatedBoolean(HISTORICAL), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeTristateOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeTristateOptionValue.kt index 77dcf736..c1b5a674 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeTristateOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeTristateOptionValue.kt @@ -1,20 +1,19 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for CAKE.UseRawPacketSize - * C Function: config_parse_cake_tristate(QDISC_KIND_CAKE) - * Used by Options: CAKE.UseRawPacketSize - * - * Accepts tristate boolean values: yes/no/true/false/on/off/1/0 + * Validator for CAKE.UseRawPacketSize (and AutoRateIngress=, NAT=, Wash=, SplitGSO=) + * C Function: config_parse_cake_tristate(QDISC_KIND_CAKE), src/network/tc/cake.c + * + * Reads the value with parse_tristate() -> parse_tristate_full() -> parse_boolean(), so it is a + * plain boolean in any casing (the empty value is handled by the framework). */ class ConfigParseCakeTristateOptionValue : SimpleGrammarOptionValues( "config_parse_cake_tristate", SequenceCombinator( - FlexibleLiteralChoiceTerminal("1", "yes", "y", "true", "t", "on", "0", "no", "n", "false", "f", "off"), + BOOLEAN, EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDfOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDfOptionValue.kt index 348b2cc2..538eedca 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDfOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDfOptionValue.kt @@ -1,21 +1,20 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for VXLAN.IPDoNotFragment - * C Function: config_parse_df(0) - * Used by Options: VXLAN.IPDoNotFragment - * - * Validates the IPv4 Do not Fragment (DF) bit setting. - * Accepts boolean values (yes/no/1/0/true/false/on/off/y/n/t/f) or "inherit". + * C Function: config_parse_df(0), src/network/netdev/vxlan.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(df, ...) over df_table (no, yes, inherit). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseDfOptionValue : SimpleGrammarOptionValues( "config_parse_df", SequenceCombinator( - FlexibleLiteralChoiceTerminal("1", "yes", "y", "true", "t", "on", "0", "no", "n", "false", "f", "off", "inherit"), + namesOrBoolean("no", "yes", "inherit"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionValue.kt index 013c40bb..4f986311 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionValue.kt @@ -1,21 +1,32 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Network.DHCP - * C Function: config_parse_dhcp(0) - * Used by Options: Network.DHCP - * - * Enables DHCPv4 and/or DHCPv6 client support. - * Accepts: yes, no, ipv4, or ipv6 + * C Function: config_parse_dhcp(0), src/network/networkd-dhcp-common.c + * + * address_family_from_string first — DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over address_family_table + * (no, yes, ipv4, ipv6) in src/network/networkd-util.c, so any boolean in any casing — then the + * case-sensitive dhcp_deprecated_address_family_table (none, both, v4, v6), which is accepted with a + * "DHCP=%s is deprecated" warning. */ class ConfigParseDhcpOptionValue : SimpleGrammarOptionValues( "config_parse_dhcp", SequenceCombinator( - FlexibleLiteralChoiceTerminal("yes", "no", "ipv4", "ipv6"), + // One names terminal (not two alternatives) so `none` is tried before `no` — the choices are + // matched longest first. + namesOrBoolean( + FlexibleLiteralChoiceTerminal("no", "yes", "ipv4", "ipv6", "none", "both", "v4", "v6").deprecating( + mapOf( + "none" to "DHCP=none is deprecated, please use DHCP=no instead.", + "both" to "DHCP=both is deprecated, please use DHCP=yes instead.", + "v4" to "DHCP=v4 is deprecated, please use DHCP=ipv4 instead.", + "v6" to "DHCP=v6 is deprecated, please use DHCP=ipv6 instead.", + ) + ) + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerPersistLeasesOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerPersistLeasesOptionValue.kt index 63f44e92..a03a30d1 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerPersistLeasesOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerPersistLeasesOptionValue.kt @@ -1,18 +1,21 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for DHCPServer.PersistLeases - * C Function: config_parse_dhcp_server_persist_leases(0) - * Used by Options: DHCPServer.PersistLeases + * C Function: config_parse_dhcp_server_persist_leases(0), src/network/networkd-dhcp-server.c + * + * DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN over + * dhcp_server_persist_leases_table (no, yes, runtime). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseDhcpServerPersistLeasesOptionValue : SimpleGrammarOptionValues( "config_parse_dhcp_server_persist_leases", SequenceCombinator( - FlexibleLiteralChoiceTerminal("runtime", "1", "yes", "y", "true", "t", "on", "0", "no", "n", "false", "f", "off"), + namesOrBoolean("no", "yes", "runtime"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecModeOptionValue.kt index bdf2011d..4cc66c08 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecModeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecModeOptionValue.kt @@ -1,11 +1,9 @@ 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.BOOLEAN import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.namesOrBoolean /** * Validator for Network.DNSSEC (.network) and Resolve.DNSSEC (.conf via resolved.conf). @@ -18,10 +16,8 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram class ConfigParseDnssecModeOptionValue : SimpleGrammarOptionValues( "config_parse_dnssec_mode", SequenceCombinator( - AlternativeCombinator( - FlexibleLiteralChoiceTerminal("allow-downgrade"), - BOOLEAN - ), + // dnssec_mode_table: the names are case-sensitive, the boolean half (parse_boolean) is not. + namesOrBoolean("no", "allow-downgrade", "yes"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseExecPreserveModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseExecPreserveModeOptionValue.kt index d3c6f46e..12babcb3 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseExecPreserveModeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseExecPreserveModeOptionValue.kt @@ -1,23 +1,21 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Swap.RuntimeDirectoryPreserve - * C Function: config_parse_exec_preserve_mode(0) - * Used by Options: Swap.RuntimeDirectoryPreserve - * - * Validates the preserve mode for runtime directories: - * - no: directories always removed when service stops - * - restart: directories preserved on automatic and manual restart - * - yes: directories not removed when service stops + * Validator for RuntimeDirectoryPreserve= (Service/Socket/Mount/Swap) + * C Function: config_parse_exec_preserve_mode(0), src/core/load-fragment.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over exec_preserve_mode_table + * (no, yes, restart, on-success) in src/core/execute.c. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseExecPreserveModeOptionValue : SimpleGrammarOptionValues( "config_parse_exec_preserve_mode", SequenceCombinator( - FlexibleLiteralChoiceTerminal("no", "restart", "yes"), + namesOrBoolean("no", "yes", "restart", "on-success"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveDfOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveDfOptionValue.kt index b794f3dc..9ddeb71c 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveDfOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveDfOptionValue.kt @@ -1,27 +1,20 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for GENEVE.IPDoNotFragment - * C Function: config_parse_geneve_df(0) - * Used by Options: GENEVE.IPDoNotFragment - * - * Validates the IPDoNotFragment setting for GENEVE tunnels. - * Accepts: "no", "yes", "inherit" and standard boolean values. - * The DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN macro allows boolean aliases - * in addition to the enumerated values. + * C Function: config_parse_geneve_df(0), src/network/netdev/geneve.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(geneve_df, ...) over geneve_df_table (no, yes, inherit). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseGeneveDfOptionValue : SimpleGrammarOptionValues( "config_parse_geneve_df", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - "no", "yes", "inherit", - "1", "y", "true", "t", "on", - "0", "n", "false", "f", "off" - ), + namesOrBoolean("no", "yes", "inherit"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpMasqueradeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpMasqueradeOptionValue.kt index ba42b26a..413a118c 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpMasqueradeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpMasqueradeOptionValue.kt @@ -1,30 +1,30 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Network.IPMasquerade - * C Function: config_parse_ip_masquerade(0) - * Used by Options: Network.IPMasquerade - * - * Accepts: ipv4, ipv6, both, no, and deprecated boolean values - * (yes/true/on/1 map to ipv4, no/false/off/0 map to no) + * C Function: config_parse_ip_masquerade(0), src/network/networkd-util.c + * + * Calls parse_boolean() first (case-insensitive; yes/true/on/1 map to ipv4, no/false/off/0 to no), + * then ip_masquerade_address_family_from_string, a plain case-sensitive lookup over + * ip_masquerade_address_family_table (no, both, ipv4, ipv6). */ class ConfigParseIpMasqueradeOptionValue : SimpleGrammarOptionValues( "config_parse_ip_masquerade", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - // Primary values - "ipv4", "ipv6", "both", "no", - // Deprecated boolean true values (map to ipv4) - "1", "yes", "y", "true", "t", "on", - // Deprecated boolean false values (map to no) - "0", "n", "false", "f", "off" - ).deprecating( - // systemd.network(5): the boolean forms "are now deprecated. Please use one of the - // values above" (ipv4/ipv6/both/no). Still accepted; the truthy forms map to ipv4. + namesOrBoolean( + FlexibleLiteralChoiceTerminal("ipv4", "ipv6", "both", "no"), + DEPRECATED_BOOLEAN + ), + EOF() + ) +) { + private companion object { + // systemd.network(5): the boolean forms "are now deprecated. Please use one of the values + // above" (ipv4/ipv6/both/no). Still accepted; the truthy forms map to ipv4. + val DEPRECATED_BOOLEAN = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase = true).deprecating( buildMap { for (t in listOf("1", "yes", "y", "true", "t", "on")) { put(t, "Boolean values for IPMasquerade= are deprecated; use \"ipv4\" (or \"ipv6\"/\"both\") instead.") @@ -33,7 +33,6 @@ class ConfigParseIpMasqueradeOptionValue : SimpleGrammarOptionValues( put(f, "Boolean values for IPMasquerade= are deprecated; use \"no\" instead.") } } - ), - EOF() - ) -) + ) + } +} diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6PrivacyExtensionsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6PrivacyExtensionsOptionValue.kt index 18b42d96..c4de71bd 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6PrivacyExtensionsOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6PrivacyExtensionsOptionValue.kt @@ -1,27 +1,21 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Network.IPv6PrivacyExtensions - * C Function: config_parse_ipv6_privacy_extensions(0) - * Used by Options: Network.IPv6PrivacyExtensions - * - * Accepts boolean values (yes/no/true/false/on/off/1/0/y/n/t/f) or the special values - * "prefer-public" and "kernel". + * C Function: config_parse_ipv6_privacy_extensions(0), src/network/networkd-sysctl.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over ipv6_privacy_extensions_table + * (no, prefer-public, yes, kernel). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseIpv6PrivacyExtensionsOptionValue : SimpleGrammarOptionValues( "config_parse_ipv6_privacy_extensions", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - // Boolean values - "1", "yes", "y", "true", "t", "on", - "0", "no", "n", "false", "f", "off", - // Special values - "prefer-public", "kernel" - ), + namesOrBoolean("no", "prefer-public", "yes", "kernel"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseKeepConfigurationOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseKeepConfigurationOptionValue.kt index 8a17ee71..39fd4799 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseKeepConfigurationOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseKeepConfigurationOptionValue.kt @@ -1,31 +1,22 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Network.KeepConfiguration - * C Function: config_parse_keep_configuration(0) - * Used by Options: Network.KeepConfiguration - * - * Accepts: - * - Boolean values: 1, yes, y, true, t, on, 0, no, n, false, f, off - * - Specific values: static, dynamic-on-stop, dynamic - * - Backward compatibility: dhcp, dhcp-on-stop + * C Function: config_parse_keep_configuration(0), src/network/networkd-network.c + * + * First the backward-compatibility names `dhcp` and `dhcp-on-stop` (streq, case-sensitive), then + * keep_configuration_from_string: DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over + * keep_configuration_table (no, dynamic-on-stop, dynamic, static, yes). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseKeepConfigurationOptionValue : SimpleGrammarOptionValues( "config_parse_keep_configuration", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - // Boolean values (map to yes/no) - "1", "yes", "y", "true", "t", "on", - "0", "no", "n", "false", "f", "off", - // Explicit enum values - "static", "dynamic-on-stop", "dynamic", - // Backward compatibility - "dhcp", "dhcp-on-stop" - ), + namesOrBoolean("no", "dynamic-on-stop", "dynamic", "static", "yes", "dhcp", "dhcp-on-stop"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkLocalAddressFamilyOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkLocalAddressFamilyOptionValue.kt index 5d560510..8bac5671 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkLocalAddressFamilyOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkLocalAddressFamilyOptionValue.kt @@ -1,22 +1,22 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Network.LinkLocalAddressing - * C Function: config_parse_link_local_address_family(0) - * Used by Options: Network.LinkLocalAddressing + * C Function: config_parse_link_local_address_family(0), src/network/networkd-util.c + * + * link_local_address_family_from_string: the compat names `fallback` and `fallback-ipv4` (streq), + * then address_family_from_string, a DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over + * address_family_table (no, yes, ipv4, ipv6). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseLinkLocalAddressFamilyOptionValue : SimpleGrammarOptionValues( "config_parse_link_local_address_family", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - "yes", "no", "true", "false", "1", "0", "on", "off", "y", "n", "t", "f", - "ipv4", "ipv6", - "fallback", "fallback-ipv4" - ), + namesOrBoolean("no", "yes", "ipv4", "ipv6", "fallback", "fallback-ipv4"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpModeOptionValue.kt index bdc4ba5c..af2ee738 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpModeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpModeOptionValue.kt @@ -1,24 +1,20 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Network.LLDP - * C Function: config_parse_lldp_mode(0) - * Used by Options: Network.LLDP - * - * Accepts boolean values (yes/no/true/false/on/off/1/0) or the special value "routers-only". + * C Function: config_parse_lldp_mode(0), src/network/networkd-lldp-rx.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over lldp_mode_table (no, yes, routers-only). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseLldpModeOptionValue : SimpleGrammarOptionValues( "config_parse_lldp_mode", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - "yes", "y", "true", "t", "on", "1", - "no", "n", "false", "f", "off", "0", - "routers-only" - ), + namesOrBoolean("no", "yes", "routers-only"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMemoryPressureWatchOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMemoryPressureWatchOptionValue.kt index 13cfdccd..659f4de2 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMemoryPressureWatchOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMemoryPressureWatchOptionValue.kt @@ -1,20 +1,21 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Swap.MemoryPressureWatch - * C Function: config_parse_pressure_watch(0) - * Used by Options: Swap.MemoryPressureWatch + * C Function: config_parse_pressure_watch(0), src/core/load-fragment.c * - * Accepts boolean values (yes/no/1/0/true/false/on/off/y/n/t/f) or special values (auto/skip). + * cgroup_pressure_watch_from_string: DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over + * cgroup_pressure_watch_table (no, yes, auto, skip) in src/core/cgroup.c. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseMemoryPressureWatchOptionValue : SimpleGrammarOptionValues( "config_parse_pressure_watch", SequenceCombinator( - FlexibleLiteralChoiceTerminal("yes", "no", "1", "0", "true", "false", "on", "off", "y", "n", "t", "f", "auto", "skip"), + namesOrBoolean("no", "yes", "auto", "skip"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMulticastRouterOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMulticastRouterOptionValue.kt index 3a9a8901..9d9378b7 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMulticastRouterOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMulticastRouterOptionValue.kt @@ -1,18 +1,22 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Bridge.MulticastRouter - * C Function: config_parse_multicast_router(0) - * Used by Options: Bridge.MulticastRouter + * C Function: config_parse_multicast_router(0), src/network/netdev/bridge.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(multicast_router, MulticastRouter, _MULTICAST_ROUTER_INVALID) + * over multicast_router_table (no, query, permanent, temporary). A *true* boolean maps to + * _MULTICAST_ROUTER_INVALID and is rejected, so only the false half of the booleans is accepted. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseMulticastRouterOptionValue : SimpleGrammarOptionValues( "config_parse_multicast_router", SequenceCombinator( - FlexibleLiteralChoiceTerminal("no", "query", "permanent", "temporary"), + namesOrBoolean("no", "query", "permanent", "temporary", boolean = BOOLEAN_FALSE), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNamespaceFlagsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNamespaceFlagsOptionValue.kt index f0fc9cca..9ae10b52 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNamespaceFlagsOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNamespaceFlagsOptionValue.kt @@ -1,11 +1,11 @@ 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.BOOLEAN import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF 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.PreferValidAlternativeCombinator 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 @@ -42,7 +42,9 @@ private val NAMESPACE_LIST = SequenceCombinator( class ConfigParseNamespaceFlagsOptionValue : SimpleGrammarOptionValues( "config_parse_namespace_flags", SequenceCombinator( - AlternativeCombinator( + // PreferValid: NAMESPACE_TYPE's lenient lower-case shape would otherwise take the `t` out of a + // mixed-case boolean like `tRUE` (parse_boolean ignores case) and strand the rest. + PreferValidAlternativeCombinator( NAMESPACE_LIST, BOOLEAN ), diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNdiscStartDhcp6ClientOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNdiscStartDhcp6ClientOptionValue.kt index 5533679d..f35e84ee 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNdiscStartDhcp6ClientOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNdiscStartDhcp6ClientOptionValue.kt @@ -1,20 +1,21 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for IPv6AcceptRA.DHCPv6Client - * C Function: config_parse_ndisc_start_dhcp6_client(0) - * Used by Options: IPv6AcceptRA.DHCPv6Client - * - * Accepts boolean values (yes/no/true/false/on/off/1/0/y/n/t/f) or the special value "always". + * C Function: config_parse_ndisc_start_dhcp6_client(0), src/network/networkd-ndisc.c + * + * DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN over ndisc_start_dhcp6_client_table + * (no, always, yes). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseNdiscStartDhcp6ClientOptionValue : SimpleGrammarOptionValues( "config_parse_ndisc_start_dhcp6_client", SequenceCombinator( - FlexibleLiteralChoiceTerminal("always", "yes", "no", "true", "false", "on", "off", "1", "0", "y", "n", "t", "f"), + namesOrBoolean("no", "always", "yes"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateTmpOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateTmpOptionValue.kt index f172964e..f7e54925 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateTmpOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateTmpOptionValue.kt @@ -1,23 +1,23 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Swap.PrivateTmp - * C Function: config_parse_private_tmp(0) - * Used by Options: Swap.PrivateTmp - * - * Takes a boolean argument, or disconnected. Valid values are: - * - Boolean true: 1, yes, y, true, t, on - * - Boolean false: 0, no, n, false, f, off - * - disconnected + * Validator for PrivateTmp= (Service/Socket/Mount/Swap) + * C Function: config_parse_private_tmp(0), src/core/load-fragment.c + * + * private_tmp_from_string: DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over private_tmp_table + * (no, connected, disconnected) in src/core/namespace.c. `connected` is what a true boolean maps to + * and is accepted by the lookup even though the man page only documents booleans and `disconnected`. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case: + * `PrivateTmp=True` is valid, `PrivateTmp=Disconnected` is not. */ class ConfigParsePrivateTmpOptionValue : SimpleGrammarOptionValues( "config_parse_private_tmp", SequenceCombinator( - FlexibleLiteralChoiceTerminal("1", "yes", "y", "true", "t", "on", "0", "no", "n", "false", "f", "off", "disconnected"), + namesOrBoolean("no", "connected", "disconnected"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectControlGroupsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectControlGroupsOptionValue.kt index 375bede2..f0dcb0d1 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectControlGroupsOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectControlGroupsOptionValue.kt @@ -1,24 +1,21 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Swap.ProtectControlGroups - * C Function: config_parse_protect_control_groups(0) - * Used by Options: Swap.ProtectControlGroups - * - * Accepts boolean values (true/false/yes/no/1/0/on/off/y/n/t/f) or special values "private" and "strict". + * Validator for ProtectControlGroups= (Service/Socket/Mount/Swap) + * C Function: config_parse_protect_control_groups(0), src/core/load-fragment.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over protect_control_groups_table + * (no, yes, private, strict) in src/core/namespace.c. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseProtectControlGroupsOptionValue : SimpleGrammarOptionValues( "config_parse_protect_control_groups", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - "1", "yes", "y", "true", "t", "on", - "0", "no", "n", "false", "f", "off", - "private", "strict" - ), + namesOrBoolean("no", "yes", "private", "strict"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHomeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHomeOptionValue.kt index 9ef92965..0a1b0bb1 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHomeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHomeOptionValue.kt @@ -1,28 +1,21 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Swap.ProtectHome - * C Function: config_parse_protect_home(0) - * Used by Options: Swap.ProtectHome - * - * Validates values for ProtectHome= option which accepts: - * - Boolean values (yes, no, true, false, on, off, 1, 0, y, n, t, f) - * - Special values: read-only, tmpfs + * Validator for ProtectHome= (Service/Socket/Mount/Swap) + * C Function: config_parse_protect_home(0), src/core/load-fragment.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over protect_home_table + * (no, yes, read-only, tmpfs) in src/core/namespace.c. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseProtectHomeOptionValue : SimpleGrammarOptionValues( "config_parse_protect_home", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - // Boolean values - "1", "yes", "y", "true", "t", "on", - "0", "no", "n", "false", "f", "off", - // Special values - "read-only", "tmpfs" - ), + namesOrBoolean("no", "yes", "read-only", "tmpfs"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHostnameOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHostnameOptionValue.kt index 9534edf0..842de7a2 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHostnameOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHostnameOptionValue.kt @@ -5,15 +5,17 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram /** * Validator for Service.ProtectHostname (also Socket/Mount/Swap.ProtectHostname). - * C Function: config_parse_protect_hostname(0) + * C Function: config_parse_protect_hostname(0), src/core/load-fragment.c * * Accepts either: - * - a boolean (yes/no/y/n/true/false/t/f/1/0/on/off) or "private" - * - ":" where is a "true" boolean (yes/y/true/t/1/on) - * or "private", and matches hostname_is_valid (LDH chars; labels - * joined by '.' with no leading/trailing hyphens). + * - a boolean or "private" + * - ":" where is a "true" boolean or "private", and + * matches hostname_is_valid (LDH chars; labels joined by '.' with no leading/trailing hyphens). * - * The C code rejects "no:" (and other false synonyms with a hostname). + * The keyword goes through protect_hostname_from_string, a DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over + * protect_hostname_table (no, yes, private) in src/core/namespace.c: the boolean half ignores case + * (parse_boolean), the names do not. The C code rejects "no:" (and other false synonyms + * with a hostname). */ class ConfigParseProtectHostnameOptionValue : SimpleGrammarOptionValues( "config_parse_protect_hostname", @@ -21,31 +23,18 @@ class ConfigParseProtectHostnameOptionValue : SimpleGrammarOptionValues( AlternativeCombinator( // Form: : SequenceCombinator( - BOOLEAN_TRUE_OR_PRIVATE, + namesOrBoolean("yes", "private", boolean = BOOLEAN_TRUE), COLON_LITERAL, HOSTNAME ), // Form: standalone keyword (any boolean or "private") - BOOLEAN_OR_PRIVATE + namesOrBoolean("no", "yes", "private") ), EOF() ) ) { companion object { private val COLON_LITERAL = LiteralChoiceTerminal(":") - - // Boolean true synonyms plus "private" (used when a hostname is supplied). - private val BOOLEAN_TRUE_OR_PRIVATE = FlexibleLiteralChoiceTerminal( - "yes", "y", "true", "t", "on", "1", "private" - ) - - // Any boolean (true or false synonyms) plus "private". - private val BOOLEAN_OR_PRIVATE = FlexibleLiteralChoiceTerminal( - "1", "yes", "y", "true", "t", "on", - "0", "no", "n", "false", "f", "off", - "private" - ) - // Hostname: one or more LDH labels separated by '.'. Each label starts and // ends with [A-Za-z0-9] and may contain hyphens internally. private val HOSTNAME = RegexTerminal( diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectSystemOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectSystemOptionValue.kt index c93a2d10..60ea2367 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectSystemOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectSystemOptionValue.kt @@ -1,24 +1,21 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Swap.ProtectSystem - * C Function: config_parse_protect_system(0) - * Used by Options: Swap.ProtectSystem - * - * Accepts boolean values or the special values "full" or "strict". + * Validator for ProtectSystem= (Service/Socket/Mount/Swap) + * C Function: config_parse_protect_system(0), src/core/load-fragment.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over protect_system_table + * (no, yes, full, strict) in src/core/namespace.c. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseProtectSystemOptionValue : SimpleGrammarOptionValues( "config_parse_protect_system", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - "1", "yes", "y", "true", "t", "on", - "0", "no", "n", "false", "f", "off", - "full", "strict" - ), + namesOrBoolean("no", "yes", "full", "strict"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSocketDeferTriggerOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSocketDeferTriggerOptionValue.kt index 07153f4c..4863ae2c 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSocketDeferTriggerOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSocketDeferTriggerOptionValue.kt @@ -1,18 +1,21 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** * Validator for Socket.DeferTrigger - * C Function: config_parse_socket_defer_trigger(0) - * Used by Options: Socket.DeferTrigger + * C Function: config_parse_socket_defer_trigger(0), src/core/load-fragment.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over socket_defer_trigger_table + * (no, yes, patient) in src/core/socket.c. + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseSocketDeferTriggerOptionValue : SimpleGrammarOptionValues( "config_parse_socket_defer_trigger", SequenceCombinator( - FlexibleLiteralChoiceTerminal("patient", "yes", "y", "true", "t", "on", "1", "no", "n", "false", "f", "off", "0"), + namesOrBoolean("no", "yes", "patient"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTimezoneModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTimezoneModeOptionValue.kt index f2db33b4..95b584d4 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTimezoneModeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTimezoneModeOptionValue.kt @@ -1,18 +1,21 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Exec.Timezone - * C Function: config_parse_timezone_mode(0) - * Used by Options: Exec.Timezone + * Validator for Exec.Timezone (.nspawn) + * C Function: config_parse_timezone_mode(0), src/nspawn/nspawn-settings.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over timezone_mode_table + * (off, copy, bind, symlink, delete, auto). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseTimezoneModeOptionValue : SimpleGrammarOptionValues( "config_parse_timezone_mode", SequenceCombinator( - FlexibleLiteralChoiceTerminal("off", "copy", "bind"), + namesOrBoolean("off", "copy", "bind", "symlink", "delete", "auto"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUseDomainsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUseDomainsOptionValue.kt index 4c8bb05a..5cedd8c9 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUseDomainsOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUseDomainsOptionValue.kt @@ -1,20 +1,20 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Network.UseDomains - * C Function: config_parse_use_domains(0) - * Used by Options: Network.UseDomains - * - * Accepts boolean values (yes/no/true/false/1/0/on/off/y/n/t/f) or the special value "route". + * Validator for Network.UseDomains (and the per-protocol UseDomains= keys) + * C Function: config_parse_use_domains(0), src/network/networkd-dns.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over use_domains_table (no, route, yes). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseUseDomainsOptionValue : SimpleGrammarOptionValues( "config_parse_use_domains", SequenceCombinator( - FlexibleLiteralChoiceTerminal("yes", "no", "true", "false", "1", "0", "on", "off", "y", "n", "t", "f", "route"), + namesOrBoolean("no", "route", "yes"), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUsernsOwnershipOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUsernsOwnershipOptionValue.kt index 4294edfa..4e469e96 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUsernsOwnershipOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUsernsOwnershipOptionValue.kt @@ -1,41 +1,21 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues /** - * Validator for Files.PrivateUsersOwnership - * C Function: config_parse_userns_ownership(0) - * Used by Options: Files.PrivateUsersOwnership - * - * Validates user namespace ownership modes: - * - off: Disable ownership adjustment - * - chown: Change ownership of files - * - map: Map ownership via user namespaces - * - * Also accepts boolean values (yes/no/true/false/on/off/1/0) due to - * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN macro. + * Validator for Files.PrivateUsersOwnership (.nspawn) + * C Function: config_parse_userns_ownership(0), src/nspawn/nspawn-settings.c + * + * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN over user_namespace_ownership_table + * (off, chown, map, foreign, auto). + * string_table_lookup_from_string_with_boolean tries parse_boolean() (case-insensitive) first, + * then a case-sensitive lookup in the table, so only the boolean half ignores case. */ class ConfigParseUsernsOwnershipOptionValue : SimpleGrammarOptionValues( "config_parse_userns_ownership", SequenceCombinator( - FlexibleLiteralChoiceTerminal( - "off", - "chown", - "map", - "yes", - "no", - "true", - "false", - "on", - "1", - "0", - "y", - "n", - "t", - "f" - ), + namesOrBoolean("off", "chown", "map", "foreign", "auto"), EOF() ) ) 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 0bf52999..bcf6d944 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 @@ -26,6 +26,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp /** * The spellings parse_boolean() accepts (src/basic/parse-util.c). Kept as an array so a caller that * needs its *own* boolean terminal can build one without reusing [BOOLEAN] — see [deprecatedBoolean]. + * + * parse_boolean() compares with STRCASE_IN_SET, so any casing is accepted (`Yes`, `TRUE`, `oFf`). + * Every terminal built from these spellings must therefore pass `ignoreCase = true`; the lower-case + * forms here stay the canonical spelling that quick-fixes suggest. */ val BOOLEAN_SPELLINGS = arrayOf("1", "yes", "y", "true", "t", "on", "0", "no", "n", "false", "f", "off") @@ -41,12 +45,14 @@ val BOOLEAN_SPELLINGS = arrayOf("1", "yes", "y", "true", "t", "on", "0", "no", " * Watch the ordering when you do: this terminal matches a *prefix* of the value, so on * `ConditionVirtualization=none` it would happily match the leading `no` and strand `ne`. Under the * classic engine AlternativeCombinator commits to the first branch that matches and never backtracks, - * so the names have to come first. + * so the names have to come first. Because this terminal ignores case it can prefix-match more than + * a lower-case one would (`No` inside `None`), and a lower-case names terminal placed before it can in + * turn strand a mixed-case boolean (`t` out of `tRUE`). [namesOrBoolean] handles both; use it. * * Do NOT call [FlexibleLiteralChoiceTerminal.deprecating] on this instance — it mutates in place and * this one is shared across every validator. */ -val BOOLEAN = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS) +val BOOLEAN = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase = true) /** * Only the *false* half of [BOOLEAN_SPELLINGS]. @@ -56,7 +62,15 @@ val BOOLEAN = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS) * example: `PreferredSource=no` forbids a DHCP-supplied source, while `PreferredSource=yes` is simply * not an address. */ -val BOOLEAN_FALSE = FlexibleLiteralChoiceTerminal("0", "no", "n", "false", "f", "off") +val BOOLEAN_FALSE = FlexibleLiteralChoiceTerminal("0", "no", "n", "false", "f", "off", ignoreCase = true) + +/** + * Only the *true* half of [BOOLEAN_SPELLINGS]. + * + * For settings that run parse_boolean() but reject a false result in some form — + * config_parse_protect_hostname accepts `yes:HOSTNAME` but not `no:HOSTNAME`. + */ +val BOOLEAN_TRUE = FlexibleLiteralChoiceTerminal("1", "yes", "y", "true", "t", "on", ignoreCase = true) /** * A fresh boolean terminal with every spelling marked deprecated for [reason]. @@ -66,7 +80,26 @@ val BOOLEAN_FALSE = FlexibleLiteralChoiceTerminal("0", "no", "n", "false", "f", * the shared [BOOLEAN] must not be poisoned. */ fun deprecatedBoolean(reason: String): FlexibleLiteralChoiceTerminal = - FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS).deprecating(BOOLEAN_SPELLINGS.associateWith { reason }) + FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase = true).deprecating(BOOLEAN_SPELLINGS.associateWith { reason }) + +/** + * "One of these names, or a boolean" — what DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN (and its + * _FROM_STRING_ variants) builds. string_table_lookup_from_string_with_boolean (src/basic/string-table.c) + * tries parse_boolean() first, which ignores case, and only then falls back to a *case-sensitive* + * streq() over the table. So `PrivateTmp=True` is valid but `PrivateTmp=Disconnected` is not: the + * [names] stay case-sensitive and the boolean half is [boolean] (case-insensitive [BOOLEAN] unless a + * caller needs [BOOLEAN_FALSE] or a deprecated variant). + * + * The names come first so the boolean cannot take the `no` out of a name like `none`, and the + * alternative is a [PreferValidAlternativeCombinator] so the names' lenient shape match cannot strand + * a mixed-case boolean like `tRUE` under the classic engine. + */ +fun namesOrBoolean(names: FlexibleLiteralChoiceTerminal, boolean: Combinator = BOOLEAN): Combinator = + PreferValidAlternativeCombinator(names, boolean) + +/** [namesOrBoolean] over plain, non-deprecated table [names]. */ +fun namesOrBoolean(vararg names: String, boolean: Combinator = BOOLEAN): Combinator = + namesOrBoolean(FlexibleLiteralChoiceTerminal(*names), boolean) val BYTES = RegexTerminal("[0-9]+[a-zA-Z]*\\s*", "[0-9]+[KMGT]?\\s*") val DEVICE = RegexTerminal("\\S+\\s*", "/[^\\u0000. ]+\\s*") val IOPS = RegexTerminal("[0-9]+[a-zA-Z]*\\s*", "[0-9]+[KMGT]?\\s*") @@ -460,9 +493,10 @@ val CONDITION_ARCHITECTURE = FlexibleLiteralChoiceTerminal( /** * A virtualization parameter — condition_test_virtualization. Accepts, in order: `private-users`, a * boolean, the categories `vm`/`container`, or any id in virtualization_table (src/basic/virt.c). - * [BOOLEAN] must follow the names so it cannot strand the `no` inside `none`. + * [BOOLEAN] must follow the names so it cannot strand the `no` inside `none`. The boolean ignores case + * (parse_boolean) but the names do not (virtualization_from_string is a plain string-table lookup). */ -val CONDITION_VIRTUALIZATION = AlternativeCombinator( +val CONDITION_VIRTUALIZATION = namesOrBoolean( FlexibleLiteralChoiceTerminal( "vm", "container", "private-users", "none", "kvm", "amazon", "qemu", "bochs", "xen", "uml", "vmware", "oracle", "microsoft", "zvm", diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/PreferValidAlternativeCombinator.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/PreferValidAlternativeCombinator.kt new file mode 100644 index 00000000..e558fb8e --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/PreferValidAlternativeCombinator.kt @@ -0,0 +1,31 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar + +/** + * An [AlternativeCombinator] whose *syntactic* pass first looks for a branch that is already valid + * at this offset, and only falls back to the usual first-syntactic-match when none is. + * + * Under the classic SyntacticMatch/SemanticMatch engine a plain AlternativeCombinator commits to the + * first branch that *syntactically* matches. That goes wrong for "one of these names, or a boolean": + * the names terminal's lenient shape regex happily matches part of a perfectly good boolean — the `t` + * of `tRUE` (its regex is lower-case only), or the `fals` of `false` when no name is five characters + * long — and the enclosing sequence then fails at the stranded remainder. Asking each branch for a + * semantic match first lets the boolean win when it is the right reading, while an invalid value still + * falls through to the lenient branch so it can be located and offered quick-fixes. + * + * The list-of-successes engine ([parse]) already tries every branch, so it is inherited unchanged. + * + * Order still matters for the semantic pass: put the names before the boolean, so a prefix such as + * the `no` in `none` cannot be taken by the boolean. + */ +class PreferValidAlternativeCombinator(vararg tokens: Combinator) : AlternativeCombinator(*tokens) { + + override fun SyntacticMatch(value: String, offset: Int): MatchResult { + for (token in tokens) { + val match = token.SemanticMatch(value, offset) + if (match.matchResult != -1) { + return match + } + } + return super.SyntacticMatch(value, offset) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/CaseInsensitiveBooleanInspectionTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/CaseInsensitiveBooleanInspectionTest.kt new file mode 100644 index 00000000..df13cb1d --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/CaseInsensitiveBooleanInspectionTest.kt @@ -0,0 +1,185 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import org.junit.Test + +/** + * systemd's parse_boolean() (src/basic/parse-util.c) compares with STRCASE_IN_SET, so `Yes`, `TRUE` + * and `oFf` are as valid as `yes`. Settings that take "a boolean or one of these names" go through + * string_table_lookup_from_string_with_boolean (src/basic/string-table.c), which tries parse_boolean() + * first and then a *case-sensitive* streq() over the table: only the boolean half ignores case. + * + * Expectations are derived from the C at 0f0682b773. Each validator is exercised under whichever + * grammar engine the test run selects (`-Dsystemd.unit.grammarParseEngine=true` forces the new one). + */ +class CaseInsensitiveBooleanInspectionTest : AbstractUnitFileTest() { + + private fun highlights(fileName: String, text: String): Int { + setupFileInEditor(fileName, text) + enableInspection(InvalidValueInspection::class.java) + return myFixture.doHighlighting().size + } + + private fun assertAccepted(fileName: String, vararg lines: String) = + assertEquals(lines.joinToString(), 0, highlights(fileName, lines.joinToString("\n") + "\n")) + + /** Each of [values] for [key] in [section] is rejected on its own. */ + private fun assertEachRejected(fileName: String, section: String, key: String, vararg values: String) { + for (value in values) { + val text = "$section\n$key=$value\n" + assertEquals(text, 1, highlights(fileName, text)) + } + } + + @Test + fun testPlainBooleanAcceptsAnyCasing() { + assertAccepted( + "f.service", + "[Service]", + "PrivatePIDs=Yes", + "PrivatePIDs=TRUE", + "PrivatePIDs=oFf", + "PrivatePIDs=N", + "PrivatePIDs=T", + "PrivatePIDs=On", + ) + assertEachRejected("f.service", "[Service]", "PrivatePIDs", "yess", "Truee", "2") + } + + @Test + fun testPrivateTmpBooleanHalfIgnoresCaseButNamesDoNot() { + assertAccepted( + "f.service", + "[Service]", + "PrivateTmp=True", + "PrivateTmp=FALSE", + "PrivateTmp=Yes", + "PrivateTmp=oFf", + "PrivateTmp=tRUE", + "PrivateTmp=disconnected", + "PrivateTmp=connected", + ) + assertEachRejected("f.service", "[Service]", "PrivateTmp", "Disconnected", "DISCONNECTED", "Connected", "yess") + } + + @Test + fun testPrivateUsersAcceptsMixedCaseBoolean() { + assertAccepted("f.service", "[Service]", "PrivateUsers=False", "PrivateUsers=TRUE", "PrivateUsers=oFf") + assertAccepted("f.nspawn", "[Exec]", "PrivateUsers=False", "PrivateUsers=Yes", "PrivateUsers=pick") + assertEachRejected("f.nspawn", "[Exec]", "PrivateUsers", "Pick", "Identity", "yess") + } + + @Test + fun testProtectSettings() { + assertAccepted( + "f.service", + "[Service]", + "ProtectSystem=Yes", + "ProtectSystem=strict", + "ProtectHome=TRUE", + "ProtectHome=read-only", + "ProtectControlGroups=On", + "ProtectControlGroups=private", + "ProtectHostname=Yes", + "ProtectHostname=fALSE", + "ProtectHostname=private", + "ProtectHostname=tRUE:myhost", + "ProtectHostname=Yes:my-host.example", + "RuntimeDirectoryPreserve=Yes", + "RuntimeDirectoryPreserve=on-success", + "MemoryPressureWatch=On", + "MemoryPressureWatch=skip", + "RestrictNamespaces=tRUE", + "RestrictNamespaces=~net ipc", + "Delegate=Yes", + "Delegate=cpu io", + ) + assertEachRejected("f.service", "[Service]", "ProtectSystem", "Strict", "FULL") + assertEachRejected("f.service", "[Service]", "ProtectHome", "Read-Only", "Tmpfs") + assertEachRejected("f.service", "[Service]", "ProtectControlGroups", "Private") + // A false boolean with a hostname is refused (t == PROTECT_HOSTNAME_NO && h), in any casing. + assertEachRejected("f.service", "[Service]", "ProtectHostname", "Private", "No:myhost", "fALSE:myhost") + assertEachRejected("f.service", "[Service]", "RuntimeDirectoryPreserve", "Restart") + assertEachRejected("f.service", "[Service]", "MemoryPressureWatch", "Auto") + assertEachRejected("f.service", "[Service]", "RestrictNamespaces", "NET") + } + + @Test + fun testSocketDeferTrigger() { + assertAccepted("f.socket", "[Socket]", "DeferTrigger=TRUE", "DeferTrigger=patient") + assertEachRejected("f.socket", "[Socket]", "DeferTrigger", "Patient") + } + + @Test + fun testConditionVirtualization() { + assertAccepted( + "f.service", + "[Unit]", + "ConditionVirtualization=No", + "ConditionVirtualization=!Yes", + "ConditionVirtualization=|tRUE", + "ConditionVirtualization=none", + ) + } + + @Test + fun testNetworkSettings() { + assertAccepted( + "f.network", + "[Network]", + "DHCP=True", + "DHCP=ipv4", + "LinkLocalAddressing=Yes", + "LinkLocalAddressing=fallback", + "IPv6PrivacyExtensions=On", + "IPv6PrivacyExtensions=prefer-public", + "LLDP=TRUE", + "LLDP=routers-only", + "KeepConfiguration=Yes", + "KeepConfiguration=dhcp-on-stop", + "UseDomains=Yes", + "UseDomains=route", + "DNSSEC=Yes", + "DNSSEC=allow-downgrade", + "IPMasquerade=both", + "IPMasquerade=No", + ) + assertEachRejected("f.network", "[Network]", "DHCP", "IPv4", "yess") + assertEachRejected("f.network", "[Network]", "LLDP", "Routers-Only") + assertEachRejected("f.network", "[Network]", "KeepConfiguration", "Static") + assertEachRejected("f.network", "[Network]", "DNSSEC", "Allow-Downgrade") + assertEachRejected("f.network", "[Network]", "IPMasquerade", "IPv4", "Both") + + assertAccepted("f.network", "[Address]", "DuplicateAddressDetection=False", "DuplicateAddressDetection=none") + assertEachRejected("f.network", "[Address]", "DuplicateAddressDetection", "None", "IPv6") + + assertAccepted("f.network", "[DHCPServer]", "PersistLeases=Yes", "PersistLeases=runtime") + assertEachRejected("f.network", "[DHCPServer]", "PersistLeases", "Runtime") + + assertAccepted("f.network", "[IPv6AcceptRA]", "DHCPv6Client=Yes", "DHCPv6Client=always") + assertEachRejected("f.network", "[IPv6AcceptRA]", "DHCPv6Client", "Always") + } + + @Test + fun testNetdevSettings() { + assertAccepted("f.netdev", "[VXLAN]", "IPDoNotFragment=Yes", "IPDoNotFragment=inherit") + assertEachRejected("f.netdev", "[VXLAN]", "IPDoNotFragment", "Inherit") + + assertAccepted("f.netdev", "[GENEVE]", "IPDoNotFragment=No", "IPDoNotFragment=inherit") + assertEachRejected("f.netdev", "[GENEVE]", "IPDoNotFragment", "INHERIT") + + // [Bridge] MulticastRouter= lives in .network files. multicast_router's WITH_BOOLEAN "yes" value is + // _MULTICAST_ROUTER_INVALID, so only false booleans are accepted. + assertAccepted("f.network", "[Bridge]", "MulticastRouter=No", "MulticastRouter=FALSE", "MulticastRouter=query") + assertEachRejected("f.network", "[Bridge]", "MulticastRouter", "Yes", "true", "Query") + } + + @Test + fun testNspawnSettings() { + assertAccepted("f.nspawn", "[Exec]", "Timezone=Off", "Timezone=TRUE", "Timezone=symlink") + assertEachRejected("f.nspawn", "[Exec]", "Timezone", "Copy", "Auto") + + assertAccepted("f.nspawn", "[Files]", "PrivateUsersOwnership=Yes", "PrivateUsersOwnership=chown") + assertEachRejected("f.nspawn", "[Files]", "PrivateUsersOwnership", "Chown", "Map") + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionValueTest.kt index 52b87fb9..d6347ae6 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionValueTest.kt @@ -78,8 +78,10 @@ class ConfigParseDhcpOptionValueTest : AbstractUnitFileTest() { assertSize(0, highlights) } + // config_parse_dhcp falls back to dhcp_deprecated_address_family_table (none/both/v4/v6) with a + // deprecation warning rather than rejecting the value. @Test - fun testInvalidValueBoth() { + fun testDeprecatedValueBothIsAccepted() { // Fixture Setup // language="unit file (systemd)" val file = """ @@ -93,11 +95,12 @@ class ConfigParseDhcpOptionValueTest : AbstractUnitFileTest() { val highlights = myFixture.doHighlighting() // Verification - assertSize(1, highlights) + assertSize(0, highlights) } + // address_family_from_string is DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN, so booleans are accepted. @Test - fun testInvalidValueTrue() { + fun testBooleanTrueIsAccepted() { // Fixture Setup // language="unit file (systemd)" val file = """ @@ -111,11 +114,11 @@ class ConfigParseDhcpOptionValueTest : AbstractUnitFileTest() { val highlights = myFixture.doHighlighting() // Verification - assertSize(1, highlights) + assertSize(0, highlights) } @Test - fun testInvalidValueV4() { + fun testDeprecatedValueV4IsAccepted() { // Fixture Setup // language="unit file (systemd)" val file = """ @@ -129,7 +132,7 @@ class ConfigParseDhcpOptionValueTest : AbstractUnitFileTest() { val highlights = myFixture.doHighlighting() // Verification - assertSize(1, highlights) + assertSize(0, highlights) } @Test @@ -142,6 +145,9 @@ class ConfigParseDhcpOptionValueTest : AbstractUnitFileTest() { DHCP=no DHCP=ipv4 DHCP=ipv6 + DHCP=True + DHCP=oFf + DHCP=1 """.trimIndent() // Execute SUT @@ -161,7 +167,7 @@ class ConfigParseDhcpOptionValueTest : AbstractUnitFileTest() { [Network] DHCP=enabled DHCP=all - DHCP=v6 + DHCP=IPv4 """.trimIndent() // Execute SUT diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseExecPreserveModeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseExecPreserveModeOptionValueTest.kt index bce896c3..3ba494b0 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseExecPreserveModeOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseExecPreserveModeOptionValueTest.kt @@ -15,6 +15,11 @@ class ConfigParseExecPreserveModeOptionValueTest : AbstractUnitFileTest() { RuntimeDirectoryPreserve=no RuntimeDirectoryPreserve=restart RuntimeDirectoryPreserve=yes + RuntimeDirectoryPreserve=on-success + RuntimeDirectoryPreserve=true + RuntimeDirectoryPreserve=false + RuntimeDirectoryPreserve=Yes + RuntimeDirectoryPreserve=OFF """.trimIndent() // Execute SUT @@ -33,8 +38,8 @@ class ConfigParseExecPreserveModeOptionValueTest : AbstractUnitFileTest() { val file = """ [Swap] RuntimeDirectoryPreserve=invalid - RuntimeDirectoryPreserve=true - RuntimeDirectoryPreserve=false + RuntimeDirectoryPreserve=Restart + RuntimeDirectoryPreserve=yess RuntimeDirectoryPreserve=always """.trimIndent() diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTimezoneModeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTimezoneModeOptionValueTest.kt index 20002bea..0b5d0816 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTimezoneModeOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTimezoneModeOptionValueTest.kt @@ -15,6 +15,12 @@ class ConfigParseTimezoneModeOptionValueTest : AbstractUnitFileTest() { Timezone=off Timezone=copy Timezone=bind + Timezone=symlink + Timezone=delete + Timezone=auto + Timezone=on + Timezone=TRUE + Timezone=No """.trimIndent() // Execute SUT @@ -33,9 +39,9 @@ class ConfigParseTimezoneModeOptionValueTest : AbstractUnitFileTest() { val file = """ [Exec] Timezone=invalid - Timezone=on + Timezone=Copy Timezone=unbind - Timezone=auto + Timezone=yess """.trimIndent() // Execute SUT diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUsernsOwnershipOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUsernsOwnershipOptionValueTest.kt index b1f5c778..88fc80b1 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUsernsOwnershipOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUsernsOwnershipOptionValueTest.kt @@ -15,6 +15,8 @@ class ConfigParseUsernsOwnershipOptionValueTest : AbstractUnitFileTest() { PrivateUsersOwnership=off PrivateUsersOwnership=chown PrivateUsersOwnership=map + PrivateUsersOwnership=foreign + PrivateUsersOwnership=auto """.trimIndent() // Execute SUT @@ -43,6 +45,9 @@ class ConfigParseUsernsOwnershipOptionValueTest : AbstractUnitFileTest() { PrivateUsersOwnership=n PrivateUsersOwnership=t PrivateUsersOwnership=f + PrivateUsersOwnership=True + PrivateUsersOwnership=OFF + PrivateUsersOwnership=nO """.trimIndent() // Execute SUT @@ -61,7 +66,7 @@ class ConfigParseUsernsOwnershipOptionValueTest : AbstractUnitFileTest() { val file = """ [Files] PrivateUsersOwnership=invalid - PrivateUsersOwnership=auto + PrivateUsersOwnership=Chown PrivateUsersOwnership=chmod PrivateUsersOwnership=own """.trimIndent() From f66b5a2ca279bb3fa920b6aa7b159d46dbbc536d Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Sat, 24 Oct 2026 07:03:36 -0700 Subject: [PATCH 2/3] refactor: spell out the names-or-boolean alternative instead of namesOrBoolean() Replace the namesOrBoolean() helper with an explicit PreferValidAlternativeCombinator(FlexibleLiteralChoiceTerminal(), BOOLEAN) at every call site, so the grammar reads as the combinator structure it is and the boolean branch is never a hidden default argument. Also drop the boolean spellings (yes, no, off) from the name lists. They came from systemd's string tables, whose first entries are the canonical false/true names, but the boolean branch already accepts them in any case, so listing them again as case-sensitive names only made the lists misleading. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../ai/AddressAndNextHopFlagOptionValues.kt | 6 ++--- .../ai/ConfigParseDfOptionValue.kt | 5 +++- .../ai/ConfigParseDhcpOptionValue.kt | 7 +++--- ...ParseDhcpServerPersistLeasesOptionValue.kt | 5 +++- .../ai/ConfigParseDnssecModeOptionValue.kt | 9 +++++-- .../ConfigParseExecPreserveModeOptionValue.kt | 5 +++- .../ai/ConfigParseGeneveDfOptionValue.kt | 5 +++- .../ai/ConfigParseIpMasqueradeOptionValue.kt | 4 +-- ...igParseIpv6PrivacyExtensionsOptionValue.kt | 5 +++- ...ConfigParseKeepConfigurationOptionValue.kt | 5 +++- ...gParseLinkLocalAddressFamilyOptionValue.kt | 5 +++- .../ai/ConfigParseLldpModeOptionValue.kt | 5 +++- ...nfigParseMemoryPressureWatchOptionValue.kt | 5 +++- .../ConfigParseMulticastRouterOptionValue.kt | 5 +++- ...igParseNdiscStartDhcp6ClientOptionValue.kt | 5 +++- .../ai/ConfigParsePrivateTmpOptionValue.kt | 5 +++- ...figParseProtectControlGroupsOptionValue.kt | 5 +++- .../ai/ConfigParseProtectHomeOptionValue.kt | 5 +++- .../ConfigParseProtectHostnameOptionValue.kt | 10 ++++++-- .../ai/ConfigParseProtectSystemOptionValue.kt | 5 +++- ...onfigParseSocketDeferTriggerOptionValue.kt | 5 +++- .../ai/ConfigParseTimezoneModeOptionValue.kt | 5 +++- .../ai/ConfigParseUseDomainsOptionValue.kt | 5 +++- .../ConfigParseUsernsOwnershipOptionValue.kt | 5 +++- .../optionvalues/grammar/Combinators.kt | 25 ++++--------------- 25 files changed, 105 insertions(+), 51 deletions(-) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/AddressAndNextHopFlagOptionValues.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/AddressAndNextHopFlagOptionValues.kt index cd4c66e6..a56fcae5 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/AddressAndNextHopFlagOptionValues.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/AddressAndNextHopFlagOptionValues.kt @@ -1,7 +1,7 @@ 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.namesOrBoolean +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PreferValidAlternativeCombinator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.deprecatedBoolean import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF @@ -75,10 +75,10 @@ class ConfigParseAddressSectionDadOptionValue : SimpleGrammarOptionValues( "yes means none and no means both. Please use 'both', 'ipv4', 'ipv6' or 'none' instead." // The family names come first: a boolean terminal matches a prefix of the value, so it would - // otherwise take the leading "no" out of "none" and strand "ne". namesOrBoolean also stops the + // otherwise take the leading "no" out of "none" and strand "ne". PreferValidAlternativeCombinator stops the // four-character names' lenient shape from taking "fals" out of "false". The family names are a // case-sensitive table lookup; the boolean (parse_boolean) ignores case. - val DAD = namesOrBoolean( + val DAD = PreferValidAlternativeCombinator( FlexibleLiteralChoiceTerminal("none", "both", "ipv4", "ipv6"), deprecatedBoolean(HISTORICAL), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDfOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDfOptionValue.kt index 538eedca..2ed8abbb 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDfOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDfOptionValue.kt @@ -14,7 +14,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseDfOptionValue : SimpleGrammarOptionValues( "config_parse_df", SequenceCombinator( - namesOrBoolean("no", "yes", "inherit"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("inherit"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionValue.kt index 4f986311..ee0b271a 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionValue.kt @@ -17,15 +17,16 @@ class ConfigParseDhcpOptionValue : SimpleGrammarOptionValues( SequenceCombinator( // One names terminal (not two alternatives) so `none` is tried before `no` — the choices are // matched longest first. - namesOrBoolean( - FlexibleLiteralChoiceTerminal("no", "yes", "ipv4", "ipv6", "none", "both", "v4", "v6").deprecating( + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("ipv4", "ipv6", "none", "both", "v4", "v6").deprecating( mapOf( "none" to "DHCP=none is deprecated, please use DHCP=no instead.", "both" to "DHCP=both is deprecated, please use DHCP=yes instead.", "v4" to "DHCP=v4 is deprecated, please use DHCP=ipv4 instead.", "v6" to "DHCP=v6 is deprecated, please use DHCP=ipv6 instead.", ) - ) + ), + BOOLEAN, ), EOF() ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerPersistLeasesOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerPersistLeasesOptionValue.kt index a03a30d1..ae77c6c9 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerPersistLeasesOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerPersistLeasesOptionValue.kt @@ -15,7 +15,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseDhcpServerPersistLeasesOptionValue : SimpleGrammarOptionValues( "config_parse_dhcp_server_persist_leases", SequenceCombinator( - namesOrBoolean("no", "yes", "runtime"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("runtime"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecModeOptionValue.kt index 4cc66c08..de152848 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecModeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecModeOptionValue.kt @@ -1,9 +1,11 @@ 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.BOOLEAN import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.namesOrBoolean +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PreferValidAlternativeCombinator /** * Validator for Network.DNSSEC (.network) and Resolve.DNSSEC (.conf via resolved.conf). @@ -17,7 +19,10 @@ class ConfigParseDnssecModeOptionValue : SimpleGrammarOptionValues( "config_parse_dnssec_mode", SequenceCombinator( // dnssec_mode_table: the names are case-sensitive, the boolean half (parse_boolean) is not. - namesOrBoolean("no", "allow-downgrade", "yes"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("allow-downgrade"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseExecPreserveModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseExecPreserveModeOptionValue.kt index 12babcb3..f9ca31ac 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseExecPreserveModeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseExecPreserveModeOptionValue.kt @@ -15,7 +15,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseExecPreserveModeOptionValue : SimpleGrammarOptionValues( "config_parse_exec_preserve_mode", SequenceCombinator( - namesOrBoolean("no", "yes", "restart", "on-success"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("restart", "on-success"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveDfOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveDfOptionValue.kt index 9ddeb71c..80120910 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveDfOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveDfOptionValue.kt @@ -14,7 +14,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseGeneveDfOptionValue : SimpleGrammarOptionValues( "config_parse_geneve_df", SequenceCombinator( - namesOrBoolean("no", "yes", "inherit"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("inherit"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpMasqueradeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpMasqueradeOptionValue.kt index 413a118c..ba26b18b 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpMasqueradeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpMasqueradeOptionValue.kt @@ -14,8 +14,8 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseIpMasqueradeOptionValue : SimpleGrammarOptionValues( "config_parse_ip_masquerade", SequenceCombinator( - namesOrBoolean( - FlexibleLiteralChoiceTerminal("ipv4", "ipv6", "both", "no"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("ipv4", "ipv6", "both"), DEPRECATED_BOOLEAN ), EOF() diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6PrivacyExtensionsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6PrivacyExtensionsOptionValue.kt index c4de71bd..958efb6b 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6PrivacyExtensionsOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6PrivacyExtensionsOptionValue.kt @@ -15,7 +15,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseIpv6PrivacyExtensionsOptionValue : SimpleGrammarOptionValues( "config_parse_ipv6_privacy_extensions", SequenceCombinator( - namesOrBoolean("no", "prefer-public", "yes", "kernel"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("prefer-public", "kernel"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseKeepConfigurationOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseKeepConfigurationOptionValue.kt index 39fd4799..b2051104 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseKeepConfigurationOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseKeepConfigurationOptionValue.kt @@ -16,7 +16,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseKeepConfigurationOptionValue : SimpleGrammarOptionValues( "config_parse_keep_configuration", SequenceCombinator( - namesOrBoolean("no", "dynamic-on-stop", "dynamic", "static", "yes", "dhcp", "dhcp-on-stop"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("dynamic-on-stop", "dynamic", "static", "dhcp", "dhcp-on-stop"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkLocalAddressFamilyOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkLocalAddressFamilyOptionValue.kt index 8bac5671..bfdb6b82 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkLocalAddressFamilyOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkLocalAddressFamilyOptionValue.kt @@ -16,7 +16,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseLinkLocalAddressFamilyOptionValue : SimpleGrammarOptionValues( "config_parse_link_local_address_family", SequenceCombinator( - namesOrBoolean("no", "yes", "ipv4", "ipv6", "fallback", "fallback-ipv4"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("ipv4", "ipv6", "fallback", "fallback-ipv4"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpModeOptionValue.kt index af2ee738..0af85e15 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpModeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpModeOptionValue.kt @@ -14,7 +14,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseLldpModeOptionValue : SimpleGrammarOptionValues( "config_parse_lldp_mode", SequenceCombinator( - namesOrBoolean("no", "yes", "routers-only"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("routers-only"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMemoryPressureWatchOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMemoryPressureWatchOptionValue.kt index 659f4de2..fbfdf663 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMemoryPressureWatchOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMemoryPressureWatchOptionValue.kt @@ -15,7 +15,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseMemoryPressureWatchOptionValue : SimpleGrammarOptionValues( "config_parse_pressure_watch", SequenceCombinator( - namesOrBoolean("no", "yes", "auto", "skip"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("auto", "skip"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMulticastRouterOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMulticastRouterOptionValue.kt index 9d9378b7..b83781b2 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMulticastRouterOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMulticastRouterOptionValue.kt @@ -16,7 +16,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseMulticastRouterOptionValue : SimpleGrammarOptionValues( "config_parse_multicast_router", SequenceCombinator( - namesOrBoolean("no", "query", "permanent", "temporary", boolean = BOOLEAN_FALSE), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("query", "permanent", "temporary"), + BOOLEAN_FALSE, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNdiscStartDhcp6ClientOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNdiscStartDhcp6ClientOptionValue.kt index f35e84ee..2d8c8d76 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNdiscStartDhcp6ClientOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNdiscStartDhcp6ClientOptionValue.kt @@ -15,7 +15,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseNdiscStartDhcp6ClientOptionValue : SimpleGrammarOptionValues( "config_parse_ndisc_start_dhcp6_client", SequenceCombinator( - namesOrBoolean("no", "always", "yes"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("always"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateTmpOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateTmpOptionValue.kt index f7e54925..1f5748bd 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateTmpOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePrivateTmpOptionValue.kt @@ -17,7 +17,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParsePrivateTmpOptionValue : SimpleGrammarOptionValues( "config_parse_private_tmp", SequenceCombinator( - namesOrBoolean("no", "connected", "disconnected"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("connected", "disconnected"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectControlGroupsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectControlGroupsOptionValue.kt index f0dcb0d1..5e45570b 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectControlGroupsOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectControlGroupsOptionValue.kt @@ -15,7 +15,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseProtectControlGroupsOptionValue : SimpleGrammarOptionValues( "config_parse_protect_control_groups", SequenceCombinator( - namesOrBoolean("no", "yes", "private", "strict"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("private", "strict"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHomeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHomeOptionValue.kt index 0a1b0bb1..ef313de8 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHomeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHomeOptionValue.kt @@ -15,7 +15,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseProtectHomeOptionValue : SimpleGrammarOptionValues( "config_parse_protect_home", SequenceCombinator( - namesOrBoolean("no", "yes", "read-only", "tmpfs"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("read-only", "tmpfs"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHostnameOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHostnameOptionValue.kt index 842de7a2..81dfad74 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHostnameOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectHostnameOptionValue.kt @@ -23,12 +23,18 @@ class ConfigParseProtectHostnameOptionValue : SimpleGrammarOptionValues( AlternativeCombinator( // Form: : SequenceCombinator( - namesOrBoolean("yes", "private", boolean = BOOLEAN_TRUE), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("private"), + BOOLEAN_TRUE, + ), COLON_LITERAL, HOSTNAME ), // Form: standalone keyword (any boolean or "private") - namesOrBoolean("no", "yes", "private") + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("private"), + BOOLEAN, + ) ), EOF() ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectSystemOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectSystemOptionValue.kt index 60ea2367..d4372f66 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectSystemOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseProtectSystemOptionValue.kt @@ -15,7 +15,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseProtectSystemOptionValue : SimpleGrammarOptionValues( "config_parse_protect_system", SequenceCombinator( - namesOrBoolean("no", "yes", "full", "strict"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("full", "strict"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSocketDeferTriggerOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSocketDeferTriggerOptionValue.kt index 4863ae2c..11a7565d 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSocketDeferTriggerOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseSocketDeferTriggerOptionValue.kt @@ -15,7 +15,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseSocketDeferTriggerOptionValue : SimpleGrammarOptionValues( "config_parse_socket_defer_trigger", SequenceCombinator( - namesOrBoolean("no", "yes", "patient"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("patient"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTimezoneModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTimezoneModeOptionValue.kt index 95b584d4..07227c01 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTimezoneModeOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTimezoneModeOptionValue.kt @@ -15,7 +15,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseTimezoneModeOptionValue : SimpleGrammarOptionValues( "config_parse_timezone_mode", SequenceCombinator( - namesOrBoolean("off", "copy", "bind", "symlink", "delete", "auto"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("copy", "bind", "symlink", "delete", "auto"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUseDomainsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUseDomainsOptionValue.kt index 5cedd8c9..0e7e9691 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUseDomainsOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUseDomainsOptionValue.kt @@ -14,7 +14,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseUseDomainsOptionValue : SimpleGrammarOptionValues( "config_parse_use_domains", SequenceCombinator( - namesOrBoolean("no", "route", "yes"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("route"), + BOOLEAN, + ), EOF() ) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUsernsOwnershipOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUsernsOwnershipOptionValue.kt index 4e469e96..5adb02da 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUsernsOwnershipOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUsernsOwnershipOptionValue.kt @@ -15,7 +15,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp class ConfigParseUsernsOwnershipOptionValue : SimpleGrammarOptionValues( "config_parse_userns_ownership", SequenceCombinator( - namesOrBoolean("off", "chown", "map", "foreign", "auto"), + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("chown", "map", "foreign", "auto"), + BOOLEAN, + ), EOF() ) ) 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 bcf6d944..97c605e1 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 @@ -47,7 +47,10 @@ val BOOLEAN_SPELLINGS = arrayOf("1", "yes", "y", "true", "t", "on", "0", "no", " * classic engine AlternativeCombinator commits to the first branch that matches and never backtracks, * so the names have to come first. Because this terminal ignores case it can prefix-match more than * a lower-case one would (`No` inside `None`), and a lower-case names terminal placed before it can in - * turn strand a mixed-case boolean (`t` out of `tRUE`). [namesOrBoolean] handles both; use it. + * turn strand a mixed-case boolean (`t` out of `tRUE`). Write such settings as + * `PreferValidAlternativeCombinator(FlexibleLiteralChoiceTerminal(), BOOLEAN)`: names first, and + * leave the boolean spellings (`yes`, `no`, `off`, ...) out of the names, since this terminal already + * accepts them in any case. * * Do NOT call [FlexibleLiteralChoiceTerminal.deprecating] on this instance — it mutates in place and * this one is shared across every validator. @@ -82,24 +85,6 @@ val BOOLEAN_TRUE = FlexibleLiteralChoiceTerminal("1", "yes", "y", "true", "t", " fun deprecatedBoolean(reason: String): FlexibleLiteralChoiceTerminal = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase = true).deprecating(BOOLEAN_SPELLINGS.associateWith { reason }) -/** - * "One of these names, or a boolean" — what DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN (and its - * _FROM_STRING_ variants) builds. string_table_lookup_from_string_with_boolean (src/basic/string-table.c) - * tries parse_boolean() first, which ignores case, and only then falls back to a *case-sensitive* - * streq() over the table. So `PrivateTmp=True` is valid but `PrivateTmp=Disconnected` is not: the - * [names] stay case-sensitive and the boolean half is [boolean] (case-insensitive [BOOLEAN] unless a - * caller needs [BOOLEAN_FALSE] or a deprecated variant). - * - * The names come first so the boolean cannot take the `no` out of a name like `none`, and the - * alternative is a [PreferValidAlternativeCombinator] so the names' lenient shape match cannot strand - * a mixed-case boolean like `tRUE` under the classic engine. - */ -fun namesOrBoolean(names: FlexibleLiteralChoiceTerminal, boolean: Combinator = BOOLEAN): Combinator = - PreferValidAlternativeCombinator(names, boolean) - -/** [namesOrBoolean] over plain, non-deprecated table [names]. */ -fun namesOrBoolean(vararg names: String, boolean: Combinator = BOOLEAN): Combinator = - namesOrBoolean(FlexibleLiteralChoiceTerminal(*names), boolean) val BYTES = RegexTerminal("[0-9]+[a-zA-Z]*\\s*", "[0-9]+[KMGT]?\\s*") val DEVICE = RegexTerminal("\\S+\\s*", "/[^\\u0000. ]+\\s*") val IOPS = RegexTerminal("[0-9]+[a-zA-Z]*\\s*", "[0-9]+[KMGT]?\\s*") @@ -496,7 +481,7 @@ val CONDITION_ARCHITECTURE = FlexibleLiteralChoiceTerminal( * [BOOLEAN] must follow the names so it cannot strand the `no` inside `none`. The boolean ignores case * (parse_boolean) but the names do not (virtualization_from_string is a plain string-table lookup). */ -val CONDITION_VIRTUALIZATION = namesOrBoolean( +val CONDITION_VIRTUALIZATION = PreferValidAlternativeCombinator( FlexibleLiteralChoiceTerminal( "vm", "container", "private-users", "none", "kvm", "amazon", "qemu", "bochs", "xen", "uml", "vmware", "oracle", "microsoft", "zvm", From c68eda712d41615cd421f18427747134395833eb Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Sat, 24 Oct 2026 07:21:31 -0700 Subject: [PATCH 3/3] docs: document PreferValidAlternativeCombinator as a legacy-engine workaround Spell out that it only changes the classic SyntacticMatch pass, that it is identical to AlternativeCombinator under the new engine, and give the concrete inputs that fail without it. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../PreferValidAlternativeCombinator.kt | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/PreferValidAlternativeCombinator.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/PreferValidAlternativeCombinator.kt index e558fb8e..939cd3c0 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/PreferValidAlternativeCombinator.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/PreferValidAlternativeCombinator.kt @@ -1,21 +1,26 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar /** - * An [AlternativeCombinator] whose *syntactic* pass first looks for a branch that is already valid - * at this offset, and only falls back to the usual first-syntactic-match when none is. + * A workaround for the LEGACY (classic SyntacticMatch/SemanticMatch) engine only. Under the new + * list-of-successes engine ([parse]/validate) it behaves exactly like [AlternativeCombinator]. * - * Under the classic SyntacticMatch/SemanticMatch engine a plain AlternativeCombinator commits to the - * first branch that *syntactically* matches. That goes wrong for "one of these names, or a boolean": - * the names terminal's lenient shape regex happily matches part of a perfectly good boolean — the `t` - * of `tRUE` (its regex is lower-case only), or the `fals` of `false` when no name is five characters - * long — and the enclosing sequence then fails at the stranded remainder. Asking each branch for a - * semantic match first lets the boolean win when it is the right reading, while an invalid value still - * falls through to the lenient branch so it can be located and offered quick-fixes. + * The classic engine's [AlternativeCombinator] returns the first branch that *syntactically* matches + * and never backtracks. A [FlexibleLiteralChoiceTerminal] matches syntactically with a lenient regex + * built from its choices' characters and capped at the longest choice, so a first branch can grab + * part of a value that belongs to a later branch, and the enclosing sequence then fails on the + * stranded rest. Every one of these is valid, and every one fails with a plain AlternativeCombinator: * - * The list-of-successes engine ([parse]) already tries every branch, so it is inherited unchanged. + * - `Alt(Flex("yes", "no"), Flex("maybe"))` on `maybe`: the first regex `[a-z]{1,3}` takes `may`. + * - `Alt(Flex("ab"), Flex("abc"))` on `abc`: the exact prefix `ab` wins before `abc` is tried. + * - `Alt(Flex("none", "both", "ipv4", "ipv6"), BOOLEAN)` on `false`: the 4-char cap takes `fals`. + * - `Alt(Flex("connected", "disconnected"), BOOLEAN)` on `tRUE`: the lower-case class stops at `R`. * - * Order still matters for the semantic pass: put the names before the boolean, so a prefix such as - * the `no` in `none` cannot be taken by the boolean. + * This class first asks each branch for an exact (semantic) match and returns the first that has + * one; only if none does it fall back to the usual lenient first-match, so an invalid value can + * still be located and offered quick-fixes. Order still matters for that exact pass: put longer or + * more specific branches first (e.g. names before [BOOLEAN], so `no` cannot be taken out of `none`). + * + * Can be replaced by a plain [AlternativeCombinator] once the legacy engine is removed. */ class PreferValidAlternativeCombinator(vararg tokens: Combinator) : AlternativeCombinator(*tokens) {