diff --git a/doc/changes/changes.md b/doc/changes/changes.md index 9f618092..eea7a754 100644 --- a/doc/changes/changes.md +++ b/doc/changes/changes.md @@ -1,5 +1,6 @@ # Changes +* [4.2.0](changes_4.2.0.md) * [4.1.0](changes_4.1.0.md) * [4.0.2](changes_4.0.2.md) * [4.0.1](changes_4.0.1.md) diff --git a/doc/changes/changes_4.2.0.md b/doc/changes/changes_4.2.0.md new file mode 100644 index 00000000..69f7dca0 --- /dev/null +++ b/doc/changes/changes_4.2.0.md @@ -0,0 +1,11 @@ +# OpenFastTrace 4.2.0, released ??? + +Code name: Markdown code blocks + +## Summary + +In this release we changed the behavior of the Markdown importer, so that if we are inside a code block, no OFT specification items are found. This is a corner case, but we think that this behavior is what users would expect (#480). + +## Features + +* #480: Ignore specification items inside Markdown code blocks diff --git a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LineParserState.java b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LineParserState.java index 7bd3cc73..649eba11 100644 --- a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LineParserState.java +++ b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LineParserState.java @@ -29,6 +29,8 @@ public enum LineParserState TITLE, /** Found tags */ TAGS, + /** Code block */ + CODE_BLOCK, /** Reached the end of the file */ EOF } diff --git a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownImporter.java b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownImporter.java index 4014375b..586eb113 100644 --- a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownImporter.java +++ b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownImporter.java @@ -42,6 +42,7 @@ protected Transition[] configureTransitions() transition(START , SPEC_ITEM , MdPattern.ID , this::beginItem ), transition(START , TITLE , SECTION_TITLE , this::rememberTitle ), transition(START , START , MdPattern.FORWARD , this::forward ), + transition(START , CODE_BLOCK , MdPattern.CODE_BEGIN , () -> {} ), transition(START , START , MdPattern.EVERYTHING , () -> {} ), transition(TITLE , SPEC_ITEM , MdPattern.ID , this::beginItem ), @@ -99,7 +100,6 @@ protected Transition[] configureTransitions() transition(COMMENT , TAGS , MdPattern.TAGS , () -> {} ), transition(COMMENT , COMMENT , MdPattern.EVERYTHING , this::appendComment ), - // [impl->dsn~md.covers-list~1] transition(COVERS , SPEC_ITEM , MdPattern.ID , this::beginItem ), transition(COVERS , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), @@ -156,7 +156,9 @@ protected Transition[] configureTransitions() transition(TAGS , COVERS , MdPattern.COVERS , () -> {} ), transition(TAGS , TAGS , MdPattern.TAGS , () -> {} ), transition(TAGS , TAGS , MdPattern.TAGS_INT , this::addTag ), - transition(TAGS , START , MdPattern.FORWARD , () -> {endItem(); forward();} ) + transition(TAGS , START , MdPattern.FORWARD , () -> {endItem(); forward();} ), + + transition(CODE_BLOCK , START , MdPattern.CODE_END , () -> {} ) }; // @formatter:on } diff --git a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MdPattern.java b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MdPattern.java index b5cd0c4c..27962554 100644 --- a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MdPattern.java +++ b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MdPattern.java @@ -15,6 +15,8 @@ enum MdPattern // [impl->dsn~md.artifact-forwarding-notation~1] // @formatter:off + CODE_BEGIN(" *[`~]{3,30}\\w*\\s*"), + CODE_END(" *[`~]{3,30}\\s*"), COMMENT("Comment:\\s*"), COVERS("Covers:\\s*"), COVERS_REF(PatternConstants.REFERENCE_AFTER_BULLET), diff --git a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TestMarkdownMarkupImporter.java b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TestMarkdownMarkupImporter.java index 671435e6..c967840d 100644 --- a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TestMarkdownMarkupImporter.java +++ b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TestMarkdownMarkupImporter.java @@ -1,5 +1,6 @@ package org.itsallcode.openfasttrace.importer.markdown; +import static org.hamcrest.Matchers.emptyIterable; import static org.itsallcode.matcher.auto.AutoMatcher.contains; import static org.itsallcode.openfasttrace.testutil.core.ItemBuilderFactory.item; @@ -8,6 +9,7 @@ import org.itsallcode.openfasttrace.testutil.importer.lightweightmarkup.AbstractLightWeightMarkupImporterTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; class TestMarkdownMarkupImporter extends AbstractLightWeightMarkupImporterTest @@ -138,4 +140,79 @@ void testLessThenThreeUnderliningCharactersAreNotDetectedAsTitleUnderlines() .location("z", 3) .build())); } + + @ParameterizedTest + @CsvSource( + { + "```,```", + "``` ,``` ", + "``` ,```", + "````, ````", + " ```, ```", + " ```, ```", + " ```, ```", + "```java, ```", + "```java , ``` ", + "~~~, ~~~", + "~~~java, ~~~", + " ~~~~java, ~~~~ " + }) + void testWhenInsideMarkdownCodeBlockThenNoSpecificationItemMustBeDetected(final String startMarker, + final String endMarker) + { + assertImport("file_with_code_block.md", """ + %s + This is a code block, the following requirement must be ignored. + + req~example~1 + %s + """.formatted(startMarker, endMarker), + emptyIterable()); + } + + @ParameterizedTest + @CsvSource( + { + "`,`", + "``,``", + " ``', ``", + "`` ,`` ", + "``java,``", + "~~,~~", + }) + void testWhenNotInsideMarkdownCodeBlockThenSpecificationItemMustBeDetected(final String startMarker, + final String endMarker) + { + assertImport("file_without_code_block.md", """ + %s + This is not a code block, the following requirement must be detected. + + req~example~1 + %s + """.formatted(startMarker, endMarker), + contains(item() + .id(SpecificationItemId.parseId("req~example~1")) + .location("file_without_code_block.md", 4) + .description(endMarker) // End marker looks like part of the description in this case. + .build())); + } + + @Test + void testWhenCodeBlockIsInsideCommentSectionThenItIsImportedAsPartOfComment() + { + assertImport("file_with_code_block_in_comment.md", """ + req~comment_with_code_block~1 + Comment: + + ``` + This is a code block inside a comment. + ``` + """, + contains(item() + .id(SpecificationItemId.createId("req", "comment_with_code_block", 1)) + .comment("```" + System.lineSeparator()+ "This is a code block inside a comment." + + System.lineSeparator() + "```") + .location("file_with_code_block_in_comment.md", 1) + .build())); + } }