Skip to content

Commit dc28c7f

Browse files
ptrthomasclaude
andcommitted
fix docstring with @ sign breaking Scenario Outline parsing #2775
Two issues fixed: 1. Lexer: shouldTransitionToGherkin() didn't recognize """ as a transition trigger, so docstrings after Scenario Outline were consumed as description text. Added """ to the keyword list. 2. Parser: bare docstrings (before any step) in scenarios and outlines are now consumed as documentation rather than causing parse failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 48f1457 commit dc28c7f

File tree

3 files changed

+47
-1
lines changed

3 files changed

+47
-1
lines changed

karate-core/src/main/java/io/karatelabs/gherkin/GherkinLexer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ private TokenType scanGherkinWhitespaceWithNewline() {
120120
}
121121
}
122122
// After newline, return to appropriate state
123+
// GS_DOC_STRING must NOT be reset - content continues until closing """
123124
if (gState == GherkinState.GS_COMMENT || gState == GherkinState.GS_DESC
124125
|| gState == GherkinState.GS_TAGS || gState == GherkinState.GS_TABLE_ROW
125126
|| gState == GherkinState.GS_STEP || gState == GherkinState.GS_STEP_MATCH
@@ -283,7 +284,7 @@ private boolean shouldTransitionToGherkin() {
283284
|| rest.startsWith("And") || rest.startsWith("But") || rest.startsWith("*")
284285
|| rest.startsWith("Scenario:") || rest.startsWith("Scenario Outline:")
285286
|| rest.startsWith("Background:") || rest.startsWith("Examples:")
286-
|| rest.startsWith("@") || rest.startsWith("|");
287+
|| rest.startsWith("@") || rest.startsWith("|") || rest.startsWith("\"\"\"");
287288
}
288289

289290
// ========== GS_COMMENT State ==========

karate-core/src/main/java/io/karatelabs/gherkin/GherkinParser.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ private boolean scenario() {
146146
return false;
147147
}
148148
nameDesc();
149+
docString(); // Skip bare docstring used as documentation (before any step)
149150
while (step()) {
150151
// Collect steps
151152
}
@@ -157,6 +158,7 @@ private boolean scenarioOutline() {
157158
return false;
158159
}
159160
nameDesc();
161+
docString(); // Skip bare docstring used as documentation (before any step)
160162
while (step()) {
161163
// Collect steps
162164
}

karate-core/src/test/java/io/karatelabs/gherkin/GherkinParserTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,4 +822,47 @@ void testScenarioOutlineWithDocstringBeforeSteps() {
822822
assertEquals(3, outline.getExamplesTables().get(0).getTable().getRows().size());
823823
}
824824

825+
@Test
826+
void testDocstringWithAtSignTokenization() {
827+
// Debug: verify @ inside docstring is NOT tokenized as G_TAG
828+
String text = "Feature: test\nScenario Outline: x\n \"\"\"\n @foo\n \"\"\"\n * print '<a>'\nExamples:\n | a |\n | 1 |\n";
829+
Resource resource = Resource.text(text);
830+
GherkinLexer lexer = new GherkinLexer(resource);
831+
Token token;
832+
boolean foundTagInDocstring = false;
833+
do {
834+
token = lexer.nextToken();
835+
if (token.type == TokenType.G_TAG && token.getText().contains("foo")) {
836+
foundTagInDocstring = true;
837+
}
838+
} while (token.type != TokenType.EOF);
839+
assertFalse(foundTagInDocstring, "@foo inside docstring should NOT be tokenized as G_TAG");
840+
}
841+
842+
@Test
843+
void testDocstringWithAtSignDoesNotBreakParsing() {
844+
// https://github.com/karatelabs/karate/issues/2775
845+
// An @ inside a docstring should NOT be tokenized as a tag
846+
parseWithAst("""
847+
Feature: Docstring with @
848+
@test
849+
Scenario Outline: My outline
850+
\"\"\"
851+
@This is a doc comment describing the outline.
852+
\"\"\"
853+
* print "row: <label>"
854+
Examples:
855+
| label |
856+
| A |
857+
| B |
858+
""");
859+
assertNotNull(outline, "Outline should be parsed");
860+
assertEquals("My outline", outline.getName());
861+
assertEquals(1, outline.getSteps().size());
862+
assertEquals(1, outline.getExamplesTables().size());
863+
assertEquals(3, outline.getExamplesTables().get(0).getTable().getRows().size());
864+
// The bare docstring is consumed as documentation, not attached to any step
865+
assertNull(outline.getSteps().get(0).getDocString());
866+
}
867+
825868
}

0 commit comments

Comments
 (0)