Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,23 @@ MarkupParserResult extractCommentBlockAsDocElements(ComponentsRegistry component

private String removeCommentPrefixAndSuffix(String commentBlock) {
String trimmed = commentBlock.trim();

// Remove opening delimiter - either (** or (*
int startIndex = trimmed.startsWith("(**") ? 3 : 2;

// Remove closing delimiter - always *)
String withoutDelimiters = trimmed.substring(startIndex, trimmed.length() - 2);

return withoutDelimiters.trim();

// Check if first line has content (text on same line as opening delimiter)
String[] lines = withoutDelimiters.split("\n", 2);
boolean firstLineHasContent = lines.length > 0 && !lines[0].trim().isEmpty();

if (firstLineHasContent) {
// First line has text right after delimiter - skip it when calculating indentation
// This removes both source code indentation and comment alignment indentation
return StringUtils.stripIndentationSkipFirstLine(withoutDelimiters);
} else {
// First line is empty - content starts on next line, use regular strip
return StringUtils.stripIndentation(withoutDelimiters);
}
}

private String extractBlock(int startBlockIdx, int endBlockIdx) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import org.testingisdocumenting.znai.parser.TestComponentsRegistry

import java.nio.file.Paths

import static org.testingisdocumenting.webtau.Matchers.*

class OcamlCommentExtractorTest {
@Test
void "extract single line comment block"() {
Expand All @@ -36,16 +38,16 @@ let x = 5
@Test
void "extract multi line comment block"() {
def content = """
(* This is a
(* This is a
multi-line
comment *)
let y = 10
"""
def extractor = new OcamlCommentExtractor(content)
def result = extractor.extractCommentBlock("let y")
result.should == """This is a
multi-line
comment"""
result.should == """This is a
multi-line
comment"""
}

@Test
Expand All @@ -72,24 +74,43 @@ let docFunc x = x * 2
result.should == "This is a documentation comment"
}

@Test(expected = IllegalArgumentException)
@Test
void "throw exception when text not found"() {
def content = """
(* Some comment *)
let x = 5
"""
def extractor = new OcamlCommentExtractor(content)
extractor.extractCommentBlock("nonexistent")

code {
extractor.extractCommentBlock("nonexistent")
}.should throwException(IllegalArgumentException, contain("can't find text: nonexistent"))
}

@Test(expected = IllegalArgumentException)
@Test
void "throw exception when no comment block found before match"() {
def content = """
let x = 5
let y = 10
"""
def extractor = new OcamlCommentExtractor(content)
extractor.extractCommentBlock("let x")

code {
extractor.extractCommentBlock("let x")
}.should throwException(IllegalArgumentException, contain("can't find comment block start"))
}

@Test
void "throw exception when comment block is not properly closed"() {
def content = """
(* This comment is not closed
let x = 5
"""
def extractor = new OcamlCommentExtractor(content)

code {
extractor.extractCommentBlock("let x")
}.should throwException(IllegalArgumentException, contain("can't find comment block end"))
}

@Test
Expand Down Expand Up @@ -174,9 +195,42 @@ let transform lst = List.map (fun x -> x + 1) lst
def extractor = new OcamlCommentExtractor(content)
def elements = extractor.extractCommentBlockAsDocElements(TestComponentsRegistry.TEST_COMPONENTS_REGISTRY,
Paths.get("test.ml"), "transform").contentToListOfMaps()

elements.size().should == 1
elements[0].type.should == 'TestMarkup'
elements[0].markup.should == 'Use `List.map` to transform elements'
}

@Test
void "indented multi-line comment should not create code blocks in markdown"() {
def content = """
(* This function does something important.
It takes an argument and returns a result.
The algorithm is efficient. *)
let myFunc x = x + 1
"""
def extractor = new OcamlCommentExtractor(content)
def elements = extractor.extractCommentBlockAsDocElements(TestComponentsRegistry.TEST_COMPONENTS_REGISTRY,
Paths.get("test.ml"), "myFunc").contentToListOfMaps()

elements.size().should == 1
elements[0].type.should == 'TestMarkup'
elements[0].markup.should == 'This function does something important.\nIt takes an argument and returns a result.\nThe algorithm is efficient.'
}

@Test
void "comment with empty first line after delimiter"() {
def content = """
(*
This is the first line of content
This is the second line
*)
let x = 5
"""
def extractor = new OcamlCommentExtractor(content)
def result = extractor.extractCommentBlock("let x")

result.should == """This is the first line of content
This is the second line"""
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Fix: `:include-ocaml-comment:` properly strips indentations instead of creating a code block
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,27 @@ public static String stripIndentation(String text) {
return lines.stream().map(l -> removeIndentation(l, indentation)).collect(Collectors.joining("\n"));
}

/**
* Strips common indentation from a multi-line text, but skips the first line when calculating
* the minimum indentation. Useful when the first line has different formatting (e.g., starts
* on the same line as an opening delimiter).
*
* @param text the text to process
* @return text with indentation stripped from all lines except the first (which is trimmed)
*/
public static String stripIndentationSkipFirstLine(String text) {
String[] lines = text.split("\n", -1);
if (lines.length <= 1) {
return text.trim();
}

// Strip indentation from lines after the first
String restOfLines = String.join("\n", Arrays.copyOfRange(lines, 1, lines.length));
String strippedRest = stripIndentation(restOfLines).trim();

return lines[0].trim() + (strippedRest.isEmpty() ? "" : "\n" + strippedRest);
}

public static String extractInsideCurlyBraces(String code) {
int startIdx = code.indexOf('{');
if (startIdx == -1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,31 @@ line #_3\r""")
stripped.should == "int a = 2;\nint b = 3;"
}

@Test
void "strip indentation but skip first line"() {
// First line has no indentation, continuation lines have 3 spaces
def text = "first line\n second line\n third line"
def stripped = StringUtils.stripIndentationSkipFirstLine(text)
// First line is trimmed, continuation lines have indentation stripped
stripped.should == "first line\nsecond line\nthird line"
}

@Test
void "strip indentation skip first line with single line"() {
def text = " single line "
def stripped = StringUtils.stripIndentationSkipFirstLine(text)
// Single line is just trimmed
stripped.should == "single line"
}

@Test
void "strip indentation skip first line with first line having leading space"() {
def text = " first line\n second line\n third line"
def stripped = StringUtils.stripIndentationSkipFirstLine(text)
// First line is trimmed, continuation lines have 4-space indentation stripped
stripped.should == "first line\nsecond line\nthird line"
}

@Test
void "extracts inside curly braces"() {
def code = "{\n statement1;\n statement2}"
Expand Down