From b9280e60da863266cdfedc04026a959d44fea591 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 05:25:24 +0000 Subject: [PATCH] [patch] Accept only one declared SectionType name in a section header Section.TryParse read the header with Enum.TryParse, which also takes integers ("[42]" became an undefined SectionType), comma-separated lists ("[Verse, Chorus]" OR'd into Chorus) and padded names ("[ Verse ]"). None of those are written by ToString, so the header now has to be the exact, letters-only name of one declared member. Matching stays case-sensitive. Fixes #318 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A5MkmzQ4qxxRmHStdQ8oJA --- Semantics.Music/Section.cs | 23 ++++++++++++++++++- Semantics.Test/Music/SectionRoundTripTests.cs | 22 ++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Semantics.Music/Section.cs b/Semantics.Music/Section.cs index 9f25d478..5d20b889 100644 --- a/Semantics.Music/Section.cs +++ b/Semantics.Music/Section.cs @@ -126,7 +126,7 @@ public static bool TryParse(string? text, [NotNullWhen(true)] out Section? resul return false; } - if (!Enum.TryParse(header[1..close], out SectionType type)) + if (!TryParseSectionType(header[1..close], out SectionType type)) { return false; } @@ -163,4 +163,25 @@ public static bool TryParse(string? text, [NotNullWhen(true)] out Section? resul result = Create(type, progression, label, key); return true; } + + // Enum.TryParse alone also takes integers, comma-separated name lists and padded names, all of + // which ToString never writes. Only the exact name of one declared member is a section type. + private static bool TryParseSectionType(string name, out SectionType type) + { + type = default; + if (name.Length == 0) + { + return false; + } + + foreach (char c in name) + { + if (c is not ((>= 'A' and <= 'Z') or (>= 'a' and <= 'z'))) + { + return false; + } + } + + return Enum.TryParse(name, out type) && Enum.IsDefined(type); + } } diff --git a/Semantics.Test/Music/SectionRoundTripTests.cs b/Semantics.Test/Music/SectionRoundTripTests.cs index 98a870e1..9e08c395 100644 --- a/Semantics.Test/Music/SectionRoundTripTests.cs +++ b/Semantics.Test/Music/SectionRoundTripTests.cs @@ -30,4 +30,26 @@ public void RoundTripWithLabelAndKey() Section s = Section.Create(SectionType.Chorus, SampleProgression(), "Chorus", Key.Create(PitchClass.Create(NoteLetter.A, Accidental.Natural), Mode.Aeolian)); Assert.AreEqual(s, Section.Parse(s.ToString())); } + + [TestMethod] + [DataRow("[Verse, Chorus]\n4/4 Dm7 / G7 /")] + [DataRow("[42]\n4/4 Dm7 / G7 /")] + [DataRow("[-1]\n4/4 Dm7 / G7 /")] + [DataRow("[ Verse ]\n4/4 Dm7 / G7 /")] + [DataRow("[verse]\n4/4 Dm7 / G7 /")] + public void TryParseRejectsHeaderThatIsNotOneSectionTypeName(string text) + { + Assert.IsFalse(Section.TryParse(text, out Section? result)); + Assert.IsNull(result); + } + + [TestMethod] + public void TryParseAcceptsEverySectionTypeName() + { + foreach (SectionType type in Enum.GetValues()) + { + Section s = Section.Create(type, SampleProgression()); + Assert.AreEqual(s, Section.Parse(s.ToString())); + } + } }