Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator


/**
* Enum-style validator backed by the parser-combinator engine instead of a plain set-membership check.
*
* Same "just give me the valid choices" API as [AbstractEnumOptionValue], but by routing through the
* grammar engine each migrated enum gets precise error highlighting and replace-with-valid-choice
* quick-fixes for free. The curated choice list is still returned for autocomplete, so completion does
* not regress (a bare [grammar.GrammarOptionValue] would suggest nothing).
*
* Migration plan: move validators in [EnumOptionValues] onto this base one merge request at a time.
* This class is intentionally dormant until the first subclass exists — nothing constructs it yet, so
* [buildGrammar] is not invoked and the existing suite is unaffected.
* Routing through the grammar engine gives each enum precise error highlighting and
* replace-with-valid-choice quick-fixes. The curated choice list is still returned for
* autocomplete, so completion does not regress (a bare [grammar.GrammarOptionValue] would
* suggest nothing).
*/
abstract class AbstractGrammarEnumOptionValue(
private val validOptions: Set<String>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator



class EmergencyActionOptionValue : AbstractEnumOptionValue(validOptions, VALIDATOR_NAME){
class EmergencyActionOptionValue : AbstractGrammarEnumOptionValue(validOptions, VALIDATOR_NAME){

companion object {
private val validOptions : Set<String> = ImmutableSet.of("none", "reboot", "reboot-force", "reboot-immediate", "poweroff", "poweroff-force", "poweroff-immediate", "exit", "exit-force")
Expand All @@ -21,143 +21,144 @@ class KillModeOptionValue : AbstractGrammarEnumOptionValue(validOptions, VALIDAT
}
}

class ManagedOOMModeOptionValue : AbstractEnumOptionValue(validOptions, VALIDATOR_NAME){
class ManagedOOMModeOptionValue : AbstractGrammarEnumOptionValue(validOptions, VALIDATOR_NAME){

companion object {
private val validOptions : Set<String> = ImmutableSet.of("auto", "kill")
const val VALIDATOR_NAME = "config_parse_managed_oom_mode"
}
}

class RestartOptionValue : AbstractEnumOptionValue(validOptions, VALIDATOR_NAME) {
class RestartOptionValue : AbstractGrammarEnumOptionValue(validOptions, VALIDATOR_NAME) {

companion object {
private val validOptions: Set<String> = ImmutableSet.of("no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always")
const val VALIDATOR_NAME = "config_parse_service_restart"
}
}

class ServiceTypeOptionValue : AbstractEnumOptionValue(validOptions, VALIDATOR_NAME) {
class ServiceTypeOptionValue : AbstractGrammarEnumOptionValue(validOptions, VALIDATOR_NAME) {

companion object {
private val validOptions: Set<String> = ImmutableSet.of("simple", "forking", "oneshot", "dbus", "notify", "notify-reload", "idle", "exec")
const val VALIDATOR_NAME = "config_parse_service_type"
}
}

class ProtectProcOptionValue : AbstractEnumOptionValue(validOptions, VALIDATOR_NAME) {
class ProtectProcOptionValue : AbstractGrammarEnumOptionValue(validOptions, VALIDATOR_NAME) {
companion object {
private val validOptions: Set<String> = ImmutableSet.of("noaccess", "invisible", "ptraceable", "default")
const val VALIDATOR_NAME = "config_parse_protect_proc"

}
}
class ProcSubsetOptionValue : AbstractEnumOptionValue(validOptions, VALIDATOR_NAME) {
class ProcSubsetOptionValue : AbstractGrammarEnumOptionValue(validOptions, VALIDATOR_NAME) {
companion object {
private val validOptions: Set<String> = ImmutableSet.of("all", "pid")
const val VALIDATOR_NAME = "config_parse_proc_subset"

}
}

class KeyRingModeOptionValue: AbstractEnumOptionValue(ImmutableSet.of("inherit", "private", "shared"), VALIDATOR_NAME) {
class KeyRingModeOptionValue: AbstractGrammarEnumOptionValue(ImmutableSet.of("inherit", "private", "shared"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_exec_keyring_mode"
}
}



class PersonalityOptionValue : AbstractEnumOptionValue(ImmutableSet.of("arm64","arm64-be","arm","arm-be","alpha","arc","arc-be","cris","x86-64","x86","ia64","loongarch64","m68k","mips64-le","mips64","mips-le","mips","nios2","parisc64","parisc","ppc64-le","ppc64","ppc","ppc-le","riscv32","riscv64","s390x","s390","sh64","sh","sparc64","sparc","tilegx"), VALIDATOR_NAME) {
class PersonalityOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("arm64","arm64-be","arm","arm-be","alpha","arc","arc-be","cris","x86-64","x86","ia64","loongarch64","m68k","mips64-le","mips64","mips-le","mips","nios2","parisc64","parisc","ppc64-le","ppc64","ppc","ppc-le","riscv32","riscv64","s390x","s390","sh64","sh","sparc64","sparc","tilegx"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_personality"
}
}

class CpuSchedulingPolicyOptionValue : AbstractEnumOptionValue(ImmutableSet.of("other", "batch", "idle", "fifo", "rr"), VALIDATOR_NAME) {
class CpuSchedulingPolicyOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("other", "batch", "idle", "fifo", "rr"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_exec_cpu_sched_policy"
}
}

class NumaPolicyOptionValue : AbstractEnumOptionValue(ImmutableSet.of("default", "bind", "interleave", "local"), VALIDATOR_NAME) {
class NumaPolicyOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("default", "bind", "interleave", "local"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_numa_policy"
}
}

class IOSchedulingClassOptionValue : AbstractEnumOptionValue(ImmutableSet.of("realtime", "best-effort", "idle"), VALIDATOR_NAME) {
class IOSchedulingClassOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("realtime", "best-effort", "idle"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_exec_io_class"
}
}

class LogLevelOptionValue : AbstractEnumOptionValue(ImmutableSet.of("emerg", "alert", "crit", "err", "warning", "notice", "info", "debug"), VALIDATOR_NAME) {
class LogLevelOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("emerg", "alert", "crit", "err", "warning", "notice", "info", "debug"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_log_level"
}
}

class SysLogFacilityOptionValue : AbstractEnumOptionValue(ImmutableSet.of("kern","user", "mail", "daemon", "auth", "syslog", "lpr", "news", "uucp", "cron", "authpriv", "ftp", "local0", "local1", "local2", "local3", "local4", "local5", "local6", "local7"), VALIDATOR_NAME) {
class SysLogFacilityOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("kern","user", "mail", "daemon", "auth", "syslog", "lpr", "news", "uucp", "cron", "authpriv", "ftp", "local0", "local1", "local2", "local3", "local4", "local5", "local6", "local7"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_log_facility"
}
}

class UtmpModeOptionValue : AbstractEnumOptionValue(ImmutableSet.of("init", "login", "user"), VALIDATOR_NAME) {
class UtmpModeOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("init", "login", "user"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_exec_utmp_mode"
}
}

class DevicePolicyOptionValue : AbstractEnumOptionValue(ImmutableSet.of("auto", "closed", "strict"), VALIDATOR_NAME) {
class DevicePolicyOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("auto", "closed", "strict"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_device_policy"
}
}

class ManagedOOMPreferenceOptionValue : AbstractEnumOptionValue(ImmutableSet.of("none", "avoid", "omit"), VALIDATOR_NAME) {
class ManagedOOMPreferenceOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("none", "avoid", "omit"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_managed_oom_preference"
}
}

class ServiceExitTypeOptionValue : AbstractEnumOptionValue(ImmutableSet.of("main", "cgroup"), VALIDATOR_NAME) {
class ServiceExitTypeOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("main", "cgroup"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_service_exit_type"
}
}

class ServiceTimeoutFailureModeOptionValue : AbstractEnumOptionValue(ImmutableSet.of("terminate", "abort", "kill"), VALIDATOR_NAME) {
class ServiceTimeoutFailureModeOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("terminate", "abort", "kill"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_service_timeout_failure_mode"
}
}
class NotifyAccessOptionValue : AbstractEnumOptionValue(ImmutableSet.of("none", "main", "exec", "all"), VALIDATOR_NAME) {

class NotifyAccessOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("none", "main", "exec", "all"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_notify_access"
}
}

class OOMPolicyOptionValue : AbstractEnumOptionValue(ImmutableSet.of("continue", "stop", "kill"), VALIDATOR_NAME) {
class OOMPolicyOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("continue", "stop", "kill"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_oom_policy"
}
}

class SocketTimestampingOptionValue : AbstractEnumOptionValue(ImmutableSet.of("off", "us", "usec", "µs", "ns", "nsec"), VALIDATOR_NAME) {
class SocketTimestampingOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("off", "us", "usec", "µs", "ns", "nsec"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_socket_timestamping"
}
}

class SocketProtocolOptionValue : AbstractEnumOptionValue(ImmutableSet.of("udplite", "sctp"), VALIDATOR_NAME) {
class SocketProtocolOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("udplite", "sctp"), VALIDATOR_NAME) {
companion object {
const val VALIDATOR_NAME = "config_parse_socket_protocol"
}
}

class SocketBindOptionValue : AbstractEnumOptionValue(ImmutableSet.of("default", "both", "ipv6-only"), VALIDATOR_NAME) {
class SocketBindOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("default", "both", "ipv6-only"), VALIDATOR_NAME) {

companion object {
const val VALIDATOR_NAME = "config_parse_socket_bind"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,27 @@ class InvalidValueInspectionForManagedOOMModeOptionsTest : AbstractUnitFileTest(
// Verification
assertSize(1, highlights)
}

fun testValueStartingWithValidChoiceButWithTrailingCharactersIsFlagged() {
// Fixture Setup
// 'autox' starts with the valid choice 'auto' but has a trailing char, so it exercises the
// syntactic format-error branch (not the semantic-choice branch); this pins it is still flagged.
// language="unit file (systemd)"
val file = """
[Swap]
ManagedOOMSwap=autox
""".trimIndent()


// Execute SUT
setupFileInEditor("file.swap", file)
enableInspection(InvalidValueInspection::class.java)
val highlights = myFixture.doHighlighting()

// Verification
assertSize(1, highlights)
assertStringContains("does not match the expected format", highlights[0]!!.description)
}
}

class InvalidValueInspectionForKillModeOptionValue : AbstractUnitFileTest() {
Expand Down Expand Up @@ -633,6 +654,27 @@ class InvalidValueInspectionForPersonalityOptionValue : AbstractUnitFileTest() {
// Verification
assertSize(1, highlights)
}

fun testInvalidValueOffersReplacementQuickFixForCollidingArchitectures() {
// Fixture Setup
// Personality has the heaviest prefix-collision set (arm/arm64/arm64-be, mips/mips64/..., etc.);
// this pins that an invalid value still offers a well-formed replacement to a full-length choice.
// language="unit file (systemd)"
val file = """
[Service]
Personality=amd64
""".trimIndent()


// Execute SUT
setupFileInEditor("file.service", file)
enableInspection(InvalidValueInspection::class.java)
val highlights = myFixture.doHighlighting()

// Verification
assertSize(1, highlights)
assertContainsQuickfix(highlights[0]!!, "Replace 'amd64' with 'arm64'")
}
}

class InvalidValueInspectionForCpuSchedulingModeOptionValue : AbstractUnitFileTest() {
Expand Down Expand Up @@ -1130,6 +1172,27 @@ class InvalidValueInspectionForSocketTimestampingOptionValue : AbstractUnitFileT
// Verification
assertSize(1, highlights)
}

fun testInvalidValueOffersReplacementQuickFixForUnicodeMicrosecondChoice() {
// Fixture Setup
// The 'µs' (U+00B5) spelling is non-ASCII, so it cannot appear in the ASCII syntactic regex and
// is reachable only via the exact-startsWith path; this pins that it survives into the fix list.
// language="unit file (systemd)"
val file = """
[Socket]
Timestamping=ms
""".trimIndent()


// Execute SUT
setupFileInEditor("file.socket", file)
enableInspection(InvalidValueInspection::class.java)
val highlights = myFixture.doHighlighting()

// Verification
assertSize(1, highlights)
assertContainsQuickfix(highlights[0]!!, "Replace 'ms' with 'µs'")
}
}

class InvalidValueInspectionForSocketProtocolOptionValue : AbstractUnitFileTest() {
Expand Down
Loading