@@ -17,21 +17,41 @@ pub fn parse_jsdoc(source_text: &str, jsdoc_span_start: u32) -> (JSDocCommentPar
1717 // - Both can be optional
1818 // - Each tag is also separated by whitespace + `@`
1919 let mut comment = None ;
20+
21+ // This will collect all the @tags found in the JSDoc
2022 let mut tags = vec ! [ ] ;
2123
22- // So, find `@` to split comment and each tag.
23- // But `@` can be found inside of `{}` (e.g. `{@see link}`), it should be distinguished.
24- let mut in_braces = false ;
25- let mut in_square_braces = false ;
26- // Also, `@` is often found inside of backtick(` or ```), like markdown.
24+ // Tracks how deeply nested we are inside curly braces `{}`.
25+ // Used to ignore `@` characters inside objects or inline tag syntax like {@link ...}
26+ let mut curly_brace_depth: i32 = 0 ;
27+
28+ let mut brace_depth: i32 = 0 ;
29+
30+ // Tracks nesting inside square brackets `[]`.
31+ // Used to avoid interpreting `@` inside optional param syntax like `[param=@default]`
32+ let mut square_brace_depth: i32 = 0 ;
33+
34+ // Tracks whether we're currently inside backticks `...`
35+ // This includes inline code blocks or markdown-style code inside comments.
2736 let mut in_backticks = false ;
37+
38+ // This flag tells us if we have already found the main comment block.
39+ // The first part before any @tags is considered the comment. Everything after is a tag.
2840 let mut comment_found = false ;
29- // Parser local offsets, not for global span
41+
42+ // These mark the current span of the "draft" being read (a comment or tag block)
3043 let ( mut start, mut end) = ( 0 , 0 ) ;
3144
45+ // Turn the source into a character iterator we can peek at
3246 let mut chars = source_text. chars ( ) . peekable ( ) ;
47+
48+ // Iterate through every character in the input string
3349 while let Some ( ch) = chars. next ( ) {
34- let can_parse = !( in_braces || in_backticks || in_square_braces) ;
50+ // A `@` is only considered the start of a tag if we are not nested inside
51+ // braces, square brackets, or backtick-quoted sections
52+ let can_parse =
53+ curly_brace_depth == 0 && square_brace_depth == 0 && brace_depth == 0 && !in_backticks;
54+
3555 match ch {
3656 // NOTE: For now, only odd backtick(s) are handled.
3757 // - 1 backtick: inline code
@@ -44,10 +64,13 @@ pub fn parse_jsdoc(source_text: &str, jsdoc_span_start: u32) -> (JSDocCommentPar
4464 in_backticks = !in_backticks;
4565 }
4666 }
47- '{' => in_braces = true ,
48- '}' => in_braces = false ,
49- '[' => in_square_braces = true ,
50- ']' => in_square_braces = false ,
67+ '{' => curly_brace_depth += 1 ,
68+ '}' => curly_brace_depth = curly_brace_depth. saturating_sub ( 1 ) ,
69+ '(' => brace_depth += 1 ,
70+ ')' => brace_depth = brace_depth. saturating_sub ( 1 ) ,
71+ '[' => square_brace_depth += 1 ,
72+ ']' => square_brace_depth = square_brace_depth. saturating_sub ( 1 ) ,
73+
5174 '@' if can_parse => {
5275 let part = & source_text[ start..end] ;
5376 let span = Span :: new (
@@ -56,22 +79,24 @@ pub fn parse_jsdoc(source_text: &str, jsdoc_span_start: u32) -> (JSDocCommentPar
5679 ) ;
5780
5881 if comment_found {
82+ // We've already seen the main comment — this is a tag
5983 tags. push ( parse_jsdoc_tag ( part, span) ) ;
6084 } else {
85+ // This is the first `@` we've encountered — treat what came before as the comment
6186 comment = Some ( JSDocCommentPart :: new ( part, span) ) ;
6287 comment_found = true ;
6388 }
6489
65- // Prepare for the next draft
6690 start = end;
6791 }
6892 _ => { }
6993 }
70- // Update the current draft
94+
95+ // Move the `end` pointer forward by the character's length
7196 end += ch. len_utf8 ( ) ;
7297 }
7398
74- // If `@` not found, flush the last draft
99+ // After the loop ends, we may have one final segment left to capture
75100 if start != end {
76101 let part = & source_text[ start..end] ;
77102 let span = Span :: new (
0 commit comments