From 8ad1ba90fe1b7f8761a7c71b8327ab3c102af50f Mon Sep 17 00:00:00 2001 From: Milos Chaloupka Date: Sun, 31 Jul 2022 00:01:32 +0100 Subject: [PATCH] Fixes #60: Keep newlines in doc strings --- RELEASE_NOTES.md | 5 ++-- TickSpec.Tests/FeatureParserTest.fs | 40 +++++++++++++++++++---------- TickSpec.Tests/WithItems.feature | 1 + TickSpec/BlockParser.fs | 24 +++++++++++++---- TickSpec/LineParser.fs | 8 ++++-- 5 files changed, 55 insertions(+), 23 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 1116841e..ce5bcd30 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,7 @@ #### 2.0.3 (To be released) -* Fix to allow multiple step types on a single method (issue #55) -* Fix unit test serialization to allow running from IDE easily +* [Fix] Keep empty lines in doc strings (issue #60) +* [Fix] Allow multiple step types on a single method (issue #55) +* [Fix] Unit test serialization to allow running from IDE easily #### 2.0.2 (Released 2021/06/19) * Added library with Xunit integration by @deyanp diff --git a/TickSpec.Tests/FeatureParserTest.fs b/TickSpec.Tests/FeatureParserTest.fs index b0112657..23437f04 100644 --- a/TickSpec.Tests/FeatureParserTest.fs +++ b/TickSpec.Tests/FeatureParserTest.fs @@ -16,20 +16,30 @@ let private verifyParsing (lines: string[]) (expected: FeatureSource) = let private verifyLineParsing (lines: string[]) (expected: LineType list) = let lineParsed = lines - |> Seq.map (fun line -> + |> Seq.mapi (fun lineNumber line -> (lineNumber + 1, line)) + |> Seq.map (fun (lineNumber, line) -> let i = line.IndexOf("#") - if i = -1 then line - else line.Substring(0, i) + if i = -1 then lineNumber, line + else lineNumber, line.Substring(0, i) ) - |> Seq.filter (fun line -> line.Trim().Length > 0) - |> Seq.scan (fun (_, lastLine) line -> - let parsed = parseLine (lastLine, line) + |> Seq.scan (fun prevLine (lineNumber, lineContent) -> + let lastParsedLine = + match prevLine with + | Line (_, _, prevLineType) -> prevLineType + | IgnoredLine prevLineType -> prevLineType + + let parsed = parseLine (lastParsedLine, lineContent) match parsed with - | Some line -> (lastLine, line) + | Some line -> (lineNumber, lineContent, line) |> Line + | None when lineContent.Trim().Length = 0 -> lastParsedLine |> IgnoredLine | None -> - let e = expectingLine lastLine - Exception(e) |> raise) (FileStart, FileStart) - |> Seq.map (fun (_, line) -> line) + let e = expectingLine lastParsedLine + Exception(e) |> raise) (Line (0, "", FileStart)) + |> Seq.choose (function + | IgnoredLine _ -> None + | Line (lineNumber, lineContent, lineType) -> Some (lineNumber, lineContent, lineType) + ) + |> Seq.map (fun (_, _, line) -> line) Assert.AreEqual(expected, lineParsed) @@ -323,6 +333,7 @@ let TagsAndExamples_FeatureSource () = let featureFileWithItems_expectedDocString = StringBuilder() .AppendLine("First line") + .AppendLine() .AppendLine(" Second line") .Append("Third line") .ToString() @@ -343,6 +354,7 @@ let FileWithItems_ParseLines () = Step (WhenStep "I take a doc string") Item (Step (WhenStep "I take a doc string"), MultiLineStringStart 4) Item (Step (WhenStep "I take a doc string"), MultiLineString " First line") + Item (Step (WhenStep "I take a doc string"), MultiLineString "") Item (Step (WhenStep "I take a doc string"), MultiLineString " Second line") Item (Step (WhenStep "I take a doc string"), MultiLineString " Third line") Item (Step (WhenStep "I take a doc string"), MultiLineStringEnd) @@ -383,13 +395,13 @@ let FileWithItems_ParseBlocks () = } { Step = ThenStep "I can take a bullet list" - LineNumber = 13 + LineNumber = 14 LineString = "Then I can take a bullet list" Item = Some (BulletsItem [ "First item"; "Second item" ]) } { Step = ThenStep "Even the next step is clear" - LineNumber = 16 + LineNumber = 17 LineString = "And Even the next step is clear" Item = None } @@ -427,14 +439,14 @@ let FileWithItems_ParseFeature () = Doc = Some featureFileWithItems_expectedDocString }) (ThenStep "I can take a bullet list", { - Number = 13 + Number = 14 Text = "Then I can take a bullet list" Bullets = Some [| "First item"; "Second item" |] Table = None Doc = None }) (ThenStep "Even the next step is clear", { - Number = 16 + Number = 17 Text = "And Even the next step is clear" Bullets = None Table = None diff --git a/TickSpec.Tests/WithItems.feature b/TickSpec.Tests/WithItems.feature index 803b298b..02e7500e 100644 --- a/TickSpec.Tests/WithItems.feature +++ b/TickSpec.Tests/WithItems.feature @@ -7,6 +7,7 @@ Given I have a table When I take a doc string """ First line + Second line Third line """ diff --git a/TickSpec/BlockParser.fs b/TickSpec/BlockParser.fs index 4067022b..1c8a907e 100644 --- a/TickSpec/BlockParser.fs +++ b/TickSpec/BlockParser.fs @@ -230,6 +230,12 @@ let private parseFeatureFile parsedLines = | (_,_,FileStart) :: xs -> parseFeatureBlock xs | _ -> parsedLines |> raiseParseException "Unexpected call of parser" +type internal ScannedLine = + // Scanned line: (lineNumber, lineContent, lineType) of parsed line + | Line of int * string * LineType + // Ignored line: lineType of last preceeding parsed non-ignored line + | IgnoredLine of LineType + let parseBlocks (lines:string seq) = lines |> Seq.mapi (fun lineNumber line -> (lineNumber + 1, line)) @@ -238,16 +244,24 @@ let parseBlocks (lines:string seq) = if i = -1 then lineNumber, line else lineNumber, line.Substring(0, i) ) - |> Seq.filter (fun (_, line) -> line.Trim().Length > 0) - |> Seq.scan(fun (_, _, lastParsedLine) (lineNumber, lineContent) -> + |> Seq.scan(fun prevLine (lineNumber, lineContent) -> + let lastParsedLine = + match prevLine with + | Line (_, _, prevLineType) -> prevLineType + | IgnoredLine prevLineType -> prevLineType let parsed = parseLine (lastParsedLine, lineContent) match parsed with - | Some line -> (lineNumber, lineContent, line) + | Some line -> (lineNumber, lineContent, line) |> Line + | None when lineContent.Trim().Length = 0 -> + lastParsedLine |> IgnoredLine | None -> let e = expectingLine lastParsedLine let m = sprintf "Syntax error on line %d %s\r\n%s" lineNumber lineContent e ParseException(m, Some lineNumber) |> raise - ) (0, "", FileStart) + ) (Line (0, "", FileStart)) + |> Seq.choose (function + | IgnoredLine _ -> None + | Line (lineNumber, lineContent, lineType) -> Some (lineNumber, lineContent, lineType) + ) |> Seq.toList |> parseFeatureFile - diff --git a/TickSpec/LineParser.fs b/TickSpec/LineParser.fs index 1a375c6d..540ddcd0 100644 --- a/TickSpec/LineParser.fs +++ b/TickSpec/LineParser.fs @@ -1,5 +1,6 @@ module internal TickSpec.LineParser +open System open System.Text.RegularExpressions type internal ItemType = @@ -45,6 +46,9 @@ let startsWith (pattern:string) (s:string) = s.StartsWith(pattern, System.StringComparison.InvariantCultureIgnoreCase) let (|Trim|) (s:string) = s.Trim() +let (|NonEmpty|_|) (s:string) = + if String.IsNullOrWhiteSpace(s) then None + else s |> Some let (|FeatureLine|_|) s = tryRegex s "^\s*Feature:\s(.*)" |> Option.map (function Trim t -> FeatureLine t) @@ -168,8 +172,8 @@ let parseLine = function | Item(_, MultiLineStringEnd), SharedExamplesLine | TagLine _, SharedExamplesLine -> SharedExamples |> Some - | FeatureName _, line -> FeatureDescription line |> Some - | FeatureDescription _, line -> FeatureDescription line |> Some + | FeatureName _, NonEmpty line -> FeatureDescription line |> Some + | FeatureDescription _, NonEmpty line -> FeatureDescription line |> Some | _, _ -> None let expectingLine = function