fix(semantic/jsdoc): Skip parsing @ inside of backticks (#3017)

This PR aims to support these cases.

````js
/**
 * This is normal comment, `@xxx` should not parsed as tag.
 *
 * @example ```ts
    // @comment
    @decoratorInComment
    class Foo { }
   ```
 */
````

Only `@example` should be parsed as tag.
This commit is contained in:
Yuji Sugiura 2024-04-18 20:18:46 +09:00 committed by GitHub
parent 395ad76410
commit 2c325ef3d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 44 additions and 2 deletions

View file

@ -304,4 +304,30 @@ line2
let (type_part, comment_part) = tag.type_comment();
assert_eq!((type_part, comment_part.parsed()), (None, "flattened data".to_string()));
}
#[test]
fn parses_with_backticks() {
let allocator = Allocator::default();
let semantic = build_semantic(
&allocator,
"
/**
* This is normal comment, `@xxx` should not parsed as tag.
*
* @example ```ts
// @comment
@decoratorInComment
class Foo { }
```
*/
",
);
let jsdoc = semantic.jsdoc().iter_all().next().unwrap();
let mut tags = jsdoc.tags().iter();
assert_eq!(tags.len(), 1);
let tag = tags.next().unwrap();
assert_eq!(tag.kind.parsed(), "example");
}
}

View file

@ -19,14 +19,30 @@ pub fn parse_jsdoc(source_text: &str, jsdoc_span_start: u32) -> (JSDocCommentPar
// So, find `@` to split comment and each tag.
// But `@` can be found inside of `{}` (e.g. `{@see link}`), it should be distinguished.
let mut in_braces = false;
// Also, `@` is often found inside of backtick(` or ```), like markdown.
let mut in_backticks = false;
let mut comment_found = false;
// Parser local offsets, not for global span
let (mut start, mut end) = (0, 0);
for ch in source_text.chars() {
let mut chars = source_text.chars().peekable();
while let Some(ch) = chars.next() {
let can_parse = !(in_braces || in_backticks);
match ch {
// NOTE: For now, only odd backtick(s) are handled.
// - 1 backtick: inline code
// - 3, 5, ... backticks: code fence
// Not so common but technically, major markdown parser can handle 3 or more backticks as code fence.
// (for nested code blocks)
// But for now, 4, 6, ... backticks are not handled here to keep things simple...
'`' => {
if chars.peek().is_some_and(|&c| c != '`') {
in_backticks = !in_backticks;
}
}
'{' => in_braces = true,
'}' => in_braces = false,
'@' if !in_braces => {
'@' if can_parse => {
let part = &source_text[start..end];
let span = Span::new(
jsdoc_span_start + u32::try_from(start).unwrap_or_default(),