diff --git a/CodeBlocker.Test/ScopesTests.cs b/CodeBlocker.Test/ScopesTests.cs index 701100f..a6305ae 100644 --- a/CodeBlocker.Test/ScopesTests.cs +++ b/CodeBlocker.Test/ScopesTests.cs @@ -121,6 +121,52 @@ public void DirectiveScopeWrapsTheBodyInAConditional() codeBlocker.ToString()); } + [TestMethod] + [DataRow("", DisplayName = "Empty")] + [DataRow(" ", DisplayName = "Whitespace")] + [DataRow(null, DisplayName = "Null")] + public void DirectiveScopeRejectsAMissingConditionBeforeWriting(string? condition) + { + using CodeBlocker codeBlocker = Create(); + + Assert.ThrowsExactly(() => new DirectiveScope(codeBlocker, condition!)); + + Assert.AreEqual(string.Empty, codeBlocker.ToString()); + } + + [TestMethod] + [DataRow("\n", DisplayName = "LF")] + [DataRow("\r\n", DisplayName = "CRLF")] + [DataRow("\r", DisplayName = "CR")] + [DataRow("\u0085", DisplayName = "NEL")] + [DataRow("\u2028", DisplayName = "LINE SEPARATOR")] + [DataRow("\u2029", DisplayName = "PARAGRAPH SEPARATOR")] + public void DirectiveScopeRejectsAConditionThatSpansLinesBeforeWriting(string terminator) + { + using CodeBlocker codeBlocker = Create(); + + Assert.ThrowsExactly(() => new DirectiveScope(codeBlocker, $"DEBUG{terminator}|| TRACE")); + + Assert.AreEqual(string.Empty, codeBlocker.ToString()); + } + + [TestMethod] + [DataRow("\n", DisplayName = "LF")] + [DataRow("\r\n", DisplayName = "CRLF")] + [DataRow("\r", DisplayName = "CR")] + [DataRow("\u0085", DisplayName = "NEL")] + [DataRow("\u2028", DisplayName = "LINE SEPARATOR")] + [DataRow("\u2029", DisplayName = "PARAGRAPH SEPARATOR")] + public void PragmaScopeRejectsWarningsThatSpanLinesBeforeWriting(string terminator) + { + using CodeBlocker codeBlocker = Create(); + + Assert.ThrowsExactly(() => new PragmaScope(codeBlocker, $"CS1591{terminator}CA1707")); + Assert.ThrowsExactly(() => new PragmaScope(codeBlocker, [$"CS1591{terminator}", "CA1707"])); + + Assert.AreEqual(string.Empty, codeBlocker.ToString()); + } + [TestMethod] public void PragmaScopeDisablesAndRestoresTheSameWarnings() { diff --git a/CodeBlocker/Scopes.cs b/CodeBlocker/Scopes.cs index bbbfb8d..2b79821 100644 --- a/CodeBlocker/Scopes.cs +++ b/CodeBlocker/Scopes.cs @@ -140,12 +140,27 @@ private static void End(CodeBlocker codeBlocker) /// /// The parent . /// The condition expression, for example NET8_0_OR_GREATER. +/// +/// is null, empty or whitespace, which would write a bare #if, or +/// contains a line terminator, which would leave the rest of the condition outside the directive. +/// Nothing is written. +/// public class DirectiveScope(CodeBlocker codeBlocker, string condition) : ScopedAction(onOpen: () => Begin(codeBlocker, condition), onClose: () => End(codeBlocker)) { private static void Begin(CodeBlocker codeBlocker, string condition) { Ensure.NotNull(codeBlocker); + if (string.IsNullOrWhiteSpace(condition)) + { + throw new ArgumentException("A conditional directive needs a condition.", nameof(condition)); + } + + if (LineTerminators.Contains(condition)) + { + throw new ArgumentException("A condition must fit on the #if line.", nameof(condition)); + } + codeBlocker.WriteLine($"#if {condition}"); } @@ -171,6 +186,10 @@ private static void End(CodeBlocker codeBlocker) /// identifier such as CS1591 or a comma-separated list. Empty or whitespace warnings are a /// no-op and emit no directives. /// +/// +/// contains a line terminator, which would leave the rest of the list +/// outside the directive. Nothing is written. +/// public class PragmaScope(CodeBlocker codeBlocker, string warnings) : ScopedAction(onOpen: () => Begin(codeBlocker, warnings), onClose: () => End(codeBlocker, warnings)) { @@ -192,6 +211,11 @@ private static void Begin(CodeBlocker codeBlocker, string warnings) return; } + if (LineTerminators.Contains(warnings)) + { + throw new ArgumentException("A warning list must fit on the #pragma line.", nameof(warnings)); + } + codeBlocker.WriteLine($"#pragma warning disable {warnings}"); }