Skip to content

Commit

Permalink
feat(semantic/jsdoc): Add Span for JSDoc, JSDocTag (#2815)
Browse files Browse the repository at this point in the history
  • Loading branch information
leaysgur authored Mar 26, 2024
1 parent c5ccd5e commit df744b2
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 106 deletions.
3 changes: 2 additions & 1 deletion crates/oxc_semantic/src/jsdoc/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ impl<'a> JSDocBuilder<'a> {
}

// Remove the very first `*`
Some(JSDoc::new(&comment_content[1..]))
let jsdoc_span = Span::new(comment_span.start + 1, comment_span.end);
Some(JSDoc::new(&comment_content[1..], jsdoc_span))
}
}

Expand Down
107 changes: 101 additions & 6 deletions crates/oxc_semantic/src/jsdoc/parser/jsdoc.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,124 @@
use super::jsdoc_tag::JSDocTag;
use super::parse::parse_jsdoc;
use oxc_span::Span;
use std::cell::OnceCell;

#[derive(Debug, Clone)]
pub struct JSDoc<'a> {
raw: &'a str,
/// Cached+parsed JSDoc comment and tags
cached: OnceCell<(String, Vec<JSDocTag<'a>>)>,
cached: OnceCell<(String, Vec<(Span, JSDocTag<'a>)>)>,
pub span: Span,
}

impl<'a> JSDoc<'a> {
/// comment_content: Inside of /**HERE*/, not include `/**` and `*/`
pub fn new(comment_content: &'a str) -> JSDoc<'a> {
Self { raw: comment_content, cached: OnceCell::new() }
/// span: `Span` for this JSDoc comment, range for `/**HERE*/`
pub fn new(comment_content: &'a str, span: Span) -> JSDoc<'a> {
Self { raw: comment_content, cached: OnceCell::new(), span }
}

pub fn comment(&self) -> &str {
&self.parse().0
}

pub fn tags(&self) -> &Vec<JSDocTag<'a>> {
pub fn tags(&self) -> &Vec<(Span, JSDocTag<'a>)> {
&self.parse().1
}

fn parse(&self) -> &(String, Vec<JSDocTag<'a>>) {
self.cached.get_or_init(|| parse_jsdoc(self.raw))
fn parse(&self) -> &(String, Vec<(Span, JSDocTag<'a>)>) {
self.cached.get_or_init(|| parse_jsdoc(self.raw, self.span.start))
}
}

#[cfg(test)]
mod test {
use crate::{Semantic, SemanticBuilder};
use oxc_allocator::Allocator;
use oxc_parser::Parser;
use oxc_span::SourceType;

fn build_semantic<'a>(
allocator: &'a Allocator,
source_text: &'a str,
source_type: Option<SourceType>,
) -> Semantic<'a> {
let source_type = source_type.unwrap_or_default();
let ret = Parser::new(allocator, source_text, source_type).parse();
let program = allocator.alloc(ret.program);
let semantic = SemanticBuilder::new(source_text, source_type)
.with_trivias(ret.trivias)
.build(program)
.semantic;
semantic
}

#[test]
fn get_jsdoc_span() {
let allocator = Allocator::default();
let semantic = build_semantic(
&allocator,
r"
/** single line */
/**
* multi
* line
*/
/**
multi
line
*/
",
Some(SourceType::default()),
);

let mut jsdocs = semantic.jsdoc().iter_all();

let jsdoc = jsdocs.next().unwrap();
assert_eq!(jsdoc.span.source_text(semantic.source_text), " single line ");
let jsdoc = jsdocs.next().unwrap();
assert_eq!(
jsdoc.span.source_text(semantic.source_text),
"\n * multi\n * line\n "
);
let jsdoc = jsdocs.next().unwrap();
assert_eq!(jsdoc.span.source_text(semantic.source_text), "\nmulti\nline\n ");
}

#[test]
fn get_jsdoc_tag_span() {
let allocator = Allocator::default();
let semantic = build_semantic(
&allocator,
r"
/** single line @k1 d1 */
/**
* multi
* line
* @k2 d2
* d2
* @k3 d3
* @k4 d4
* d4
*/
",
Some(SourceType::default()),
);

let mut jsdocs = semantic.jsdoc().iter_all();

let jsdoc = jsdocs.next().unwrap();
let mut tags = jsdoc.tags().iter();
let (span, _) = tags.next().unwrap();
assert_eq!(span.source_text(semantic.source_text), "@k1");

let jsdoc = jsdocs.next().unwrap();
let mut tags = jsdoc.tags().iter();
let (span, _) = tags.next().unwrap();
assert_eq!(span.source_text(semantic.source_text), "@k2");
let (span, _) = tags.next().unwrap();
assert_eq!(span.source_text(semantic.source_text), "@k3");
let (span, _) = tags.next().unwrap();
assert_eq!(span.source_text(semantic.source_text), "@k4");
}
}
58 changes: 29 additions & 29 deletions crates/oxc_semantic/src/jsdoc/parser/jsdoc_tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub struct JSDocTag<'a> {

impl<'a> JSDocTag<'a> {
/// kind: Does not contain the `@` prefix
/// raw_body: The body part of the tag, after the `@kind {HERE_MAY_BE_MULTILINE...}`
/// raw_body: The body part of the tag, after the `@kind{HERE_MAY_BE_MULTILINE...}`
pub fn new(kind: &'a str, raw_body: &'a str) -> JSDocTag<'a> {
debug_assert!(!kind.starts_with('@'));
Self { raw_body, kind }
Expand All @@ -51,7 +51,7 @@ impl<'a> JSDocTag<'a> {
/// @kind
/// ```
pub fn comment(&self) -> String {
utils::trim_multiline_comment(self.raw_body)
utils::trim_comment(self.raw_body)
}

/// Use for `@type`, `@satisfies`, ...etc.
Expand Down Expand Up @@ -85,7 +85,7 @@ impl<'a> JSDocTag<'a> {
None => (None, self.raw_body),
};

(type_part, utils::trim_multiline_comment(comment_part))
(type_part, utils::trim_comment(comment_part))
}

/// Use for `@param`, `@property`, `@typedef`, ...etc.
Expand Down Expand Up @@ -119,7 +119,7 @@ impl<'a> JSDocTag<'a> {
None => (None, ""),
};

(type_part, name_part, utils::trim_multiline_comment(comment_part))
(type_part, name_part, utils::trim_comment(comment_part))
}
}

Expand All @@ -130,81 +130,81 @@ mod test {
#[test]
fn parses_comment() {
assert_eq!(JSDocTag::new("a", "").comment(), "");
assert_eq!(JSDocTag::new("a", "c1").comment(), "c1");
assert_eq!(JSDocTag::new("a", " c2 \n z ").comment(), "c2\nz");
assert_eq!(JSDocTag::new("a", "* c3\n * \n z \n\n").comment(), "c3\nz");
assert_eq!(JSDocTag::new("a", " c1").comment(), "c1");
assert_eq!(JSDocTag::new("a", "\nc2 \n z ").comment(), "c2\nz");
assert_eq!(JSDocTag::new("a", "\n* c3\n * \n z \n\n").comment(), "c3\nz");
assert_eq!(
JSDocTag::new("a", "comment4 and {@inline tag}!").comment(),
JSDocTag::new("a", " comment4 and {@inline tag}!").comment(),
"comment4 and {@inline tag}!"
);
}

#[test]
fn parses_type() {
assert_eq!(JSDocTag::new("t", "{t1}").r#type(), Some("t1"));
assert_eq!(JSDocTag::new("t", "{t2} foo").r#type(), Some("t2"));
assert_eq!(JSDocTag::new("t", " {t1}").r#type(), Some("t1"));
assert_eq!(JSDocTag::new("t", "\n{t2} foo").r#type(), Some("t2"));
assert_eq!(JSDocTag::new("t", " {t3 } ").r#type(), Some("t3 "));
assert_eq!(JSDocTag::new("t", " ").r#type(), None);
assert_eq!(JSDocTag::new("t", "t4").r#type(), None);
assert_eq!(JSDocTag::new("t", "{t5 ").r#type(), None);
assert_eq!(JSDocTag::new("t", "{t6}\nx").r#type(), Some("t6"));
assert_eq!(JSDocTag::new("t", " t4").r#type(), None);
assert_eq!(JSDocTag::new("t", " {t5 ").r#type(), None);
assert_eq!(JSDocTag::new("t", " {t6}\nx").r#type(), Some("t6"));
}

#[test]
fn parses_type_comment() {
assert_eq!(JSDocTag::new("r", "{t1} c1").type_comment(), (Some("t1"), "c1".to_string()));
assert_eq!(JSDocTag::new("r", "{t2}").type_comment(), (Some("t2"), String::new()));
assert_eq!(JSDocTag::new("r", "c3").type_comment(), (None, "c3".to_string()));
assert_eq!(JSDocTag::new("r", "c4 foo").type_comment(), (None, "c4 foo".to_string()));
assert_eq!(JSDocTag::new("r", "").type_comment(), (None, String::new()));
assert_eq!(JSDocTag::new("r", " {t1} c1").type_comment(), (Some("t1"), "c1".to_string()));
assert_eq!(JSDocTag::new("r", "\n{t2}").type_comment(), (Some("t2"), String::new()));
assert_eq!(JSDocTag::new("r", " c3").type_comment(), (None, "c3".to_string()));
assert_eq!(JSDocTag::new("r", " c4 foo").type_comment(), (None, "c4 foo".to_string()));
assert_eq!(JSDocTag::new("r", " ").type_comment(), (None, String::new()));
assert_eq!(
JSDocTag::new("r", "{t5}\nc5\n...").type_comment(),
JSDocTag::new("r", "\n{t5}\nc5\n...").type_comment(),
(Some("t5"), "c5\n...".to_string())
);
assert_eq!(
JSDocTag::new("r", "{t6} - c6").type_comment(),
JSDocTag::new("r", " {t6} - c6").type_comment(),
(Some("t6"), "- c6".to_string())
);
assert_eq!(
JSDocTag::new("r", "{{ 型: t7 }} : c7").type_comment(),
JSDocTag::new("r", " {{ 型: t7 }} : c7").type_comment(),
(Some("{ 型: t7 }"), ": c7".to_string())
);
}

#[test]
fn parses_type_name_comment() {
assert_eq!(
JSDocTag::new("p", "{t1} n1 c1").type_name_comment(),
JSDocTag::new("p", " {t1} n1 c1").type_name_comment(),
(Some("t1"), Some("n1"), "c1".to_string())
);
assert_eq!(
JSDocTag::new("p", "{t2} n2").type_name_comment(),
JSDocTag::new("p", " {t2} n2").type_name_comment(),
(Some("t2"), Some("n2"), String::new())
);
assert_eq!(
JSDocTag::new("p", "n3 c3").type_name_comment(),
JSDocTag::new("p", " n3 c3").type_name_comment(),
(None, Some("n3"), "c3".to_string())
);
assert_eq!(JSDocTag::new("p", "").type_name_comment(), (None, None, String::new()));
assert_eq!(JSDocTag::new("p", "\n\n").type_name_comment(), (None, None, String::new()));
assert_eq!(
JSDocTag::new("p", "{t4} n4 c4\n...").type_name_comment(),
JSDocTag::new("p", " {t4} n4 c4\n...").type_name_comment(),
(Some("t4"), Some("n4"), "c4\n...".to_string())
);
assert_eq!(
JSDocTag::new("p", "{t5} n5 - c5").type_name_comment(),
JSDocTag::new("p", " {t5} n5 - c5").type_name_comment(),
(Some("t5"), Some("n5"), "- c5".to_string())
);
assert_eq!(
JSDocTag::new("p", "{t6}\nn6\nc6").type_name_comment(),
JSDocTag::new("p", "\n{t6}\nn6\nc6").type_name_comment(),
(Some("t6"), Some("n6"), "c6".to_string())
);
assert_eq!(
JSDocTag::new("p", "{t7}\nn7\nc\n7").type_name_comment(),
JSDocTag::new("p", "\n\n{t7}\nn7\nc\n7").type_name_comment(),
(Some("t7"), Some("n7"), "c\n7".to_string())
);
assert_eq!(
JSDocTag::new("p", "{t8}").type_name_comment(),
JSDocTag::new("p", " {t8}").type_name_comment(),
(Some("t8"), None, String::new())
);
}
Expand Down
Loading

0 comments on commit df744b2

Please sign in to comment.