Skip to content

Commit 2cc1107

Browse files
committed
fix: preserve jsdoc markdown hard breaks
1 parent f7dd1f3 commit 2cc1107

4 files changed

Lines changed: 142 additions & 10 deletions

File tree

src/generation/generate.rs

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6961,22 +6961,51 @@ fn gen_comment(comment: &Comment, context: &mut Context) -> Option<PrintItems> {
69616961
fn gen_js_doc_or_multiline_block(comment: &Comment, _context: &mut Context) -> PrintItems {
69626962
debug_assert_eq!(comment.kind, CommentKind::Block);
69636963
let is_js_doc = comment.text.starts_with('*');
6964-
return lines_to_print_items(is_js_doc, build_lines(comment));
6965-
6966-
fn build_lines(comment: &Comment) -> Vec<&str> {
6967-
let mut lines: Vec<&str> = Vec::new();
6964+
return lines_to_print_items(is_js_doc, build_lines(comment, is_js_doc));
6965+
6966+
fn build_lines(comment: &Comment, is_js_doc: bool) -> Vec<String> {
6967+
let mut lines: Vec<String> = Vec::new();
6968+
let raw_lines = utils::split_lines(&comment.text)
6969+
.map(|line| {
6970+
let text = if line.line_index == 0 && !line.text.starts_with('*') {
6971+
line.text
6972+
} else {
6973+
&line.text[get_line_start_index(line.text)..]
6974+
};
6975+
(text, line.is_last)
6976+
})
6977+
.collect::<Vec<_>>();
6978+
let mut fence_marker = None;
69686979

6969-
for line in utils::split_lines(&comment.text) {
6970-
let text = if line.line_index == 0 && !line.text.starts_with('*') {
6971-
line.text
6980+
for (index, (text, is_last)) in raw_lines.iter().enumerate() {
6981+
let has_following_non_empty_line = raw_lines
6982+
.iter()
6983+
.skip(index + 1)
6984+
.any(|(text, _)| !text.trim().is_empty());
6985+
let line_fence_marker = get_markdown_fence_marker(text);
6986+
let preserve_hard_break = is_js_doc
6987+
&& fence_marker.is_none()
6988+
&& line_fence_marker.is_none()
6989+
&& has_following_non_empty_line
6990+
&& !is_markdown_indented_code_line(text);
6991+
let text = *text;
6992+
let text = if *is_last && !text.trim().is_empty() {
6993+
text.to_string()
69726994
} else {
6973-
&line.text[get_line_start_index(line.text)..]
6995+
trim_end_preserving_markdown_hard_break(text, preserve_hard_break)
69746996
};
6975-
let text = if line.is_last && !text.trim().is_empty() { text } else { text.trim_end() };
69766997

69776998
if !text.is_empty() || !lines.last().map(|l| l.is_empty()).unwrap_or(false) {
69786999
lines.push(text);
69797000
}
7001+
7002+
if let Some(marker) = line_fence_marker {
7003+
fence_marker = match fence_marker {
7004+
Some(current_marker) if current_marker == marker => None,
7005+
Some(current_marker) => Some(current_marker),
7006+
None => Some(marker),
7007+
};
7008+
}
69807009
}
69817010

69827011
lines
@@ -6994,7 +7023,40 @@ fn gen_js_doc_or_multiline_block(comment: &Comment, _context: &mut Context) -> P
69947023
0
69957024
}
69967025

6997-
fn lines_to_print_items(is_js_doc: bool, lines: Vec<&str>) -> PrintItems {
7026+
fn get_markdown_fence_marker(text: &str) -> Option<char> {
7027+
let text = text.trim_start();
7028+
let marker = text.chars().next()?;
7029+
if marker != '`' && marker != '~' {
7030+
return None;
7031+
}
7032+
if text.chars().take_while(|&c| c == marker).count() >= 3 {
7033+
Some(marker)
7034+
} else {
7035+
None
7036+
}
7037+
}
7038+
7039+
fn is_markdown_indented_code_line(text: &str) -> bool {
7040+
let text = text.strip_prefix(' ').unwrap_or(text);
7041+
text.starts_with(" ") || text.starts_with('\t')
7042+
}
7043+
7044+
fn trim_end_preserving_markdown_hard_break(text: &str, preserve_hard_break: bool) -> String {
7045+
let trimmed = text.trim_end();
7046+
let trailing_space_count = text
7047+
.as_bytes()
7048+
.iter()
7049+
.rev()
7050+
.take_while(|&&byte| byte == b' ')
7051+
.count();
7052+
if preserve_hard_break && trailing_space_count >= 2 && !trimmed.is_empty() {
7053+
format!("{trimmed}\\")
7054+
} else {
7055+
trimmed.to_string()
7056+
}
7057+
}
7058+
7059+
fn lines_to_print_items(is_js_doc: bool, lines: Vec<String>) -> PrintItems {
69987060
let mut items = PrintItems::new();
69997061

70007062
items.push_sc(sc!("/*"));

tests/specs/comments/CommentBlocks_All.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ const t;
8787
*/
8888
const t;
8989

90+
== should not convert markdown hard line breaks in non-jsdoc block comments ==
91+
/*
92+
* Does something.
93+
* Details here.
94+
*/
95+
const t;
96+
97+
[expect]
98+
/*
99+
* Does something.
100+
* Details here.
101+
*/
102+
const t;
103+
90104
== should format comments in arguments ==
91105
call(/* test */5);
92106

tests/specs/comments/JSDocComments_All.txt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,45 @@ const test = 5;
164164
* Testing
165165
* **UNSTABLE** test */
166166
const test = 5;
167+
168+
== should convert markdown hard line breaks in js doc ==
169+
/**
170+
* Does something.
171+
* Details here.
172+
*/
173+
const test = 5;
174+
175+
[expect]
176+
/**
177+
* Does something.\
178+
* Details here.
179+
*/
180+
const test = 5;
181+
182+
== should not convert markdown hard line breaks on the last js doc content line ==
183+
/**
184+
* Does something.
185+
*/
186+
const test = 5;
187+
188+
[expect]
189+
/**
190+
* Does something.
191+
*/
192+
const test = 5;
193+
194+
== should not convert trailing spaces in js doc fenced code blocks ==
195+
/**
196+
* ```ts
197+
* const value = "x";
198+
* ```
199+
*/
200+
const test = 5;
201+
202+
[expect]
203+
/**
204+
* ```ts
205+
* const value = "x";
206+
* ```
207+
*/
208+
const test = 5;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
~~ deno: true ~~
2+
== should preserve markdown hard line breaks in jsdoc ==
3+
/**
4+
* abc
5+
* def
6+
*/
7+
export function example() {}
8+
9+
[expect]
10+
/**
11+
* abc\
12+
* def
13+
*/
14+
export function example() {}

0 commit comments

Comments
 (0)