Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(semantic/jsdoc): Rework JSDoc struct for better Span handling #2917

Merged
merged 23 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Update tests wip
  • Loading branch information
leaysgur committed Apr 8, 2024
commit 1b0ffb78d131559d7828b21ea53aa7fd3df711f9
249 changes: 118 additions & 131 deletions crates/oxc_semantic/src/jsdoc/parser/jsdoc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,134 +34,121 @@ impl<'a> JSDoc<'a> {
}
}

// #[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_comment_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 (_, span) = jsdoc.comment();
// assert_eq!(span.source_text(semantic.source_text), " single line ");

// let jsdoc = jsdocs.next().unwrap();
// let (_, span) = jsdoc.comment();
// assert_eq!(
// span.source_text(semantic.source_text),
// "\n * multi\n * line\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 tag = tags.next().unwrap();
// assert_eq!(tag.span.source_text(semantic.source_text), "@k1 d1 ");

// let jsdoc = jsdocs.next().unwrap();
// let mut tags = jsdoc.tags().iter();
// let tag = tags.next().unwrap();
// assert_eq!(
// tag.span.source_text(semantic.source_text),
// "@k2 d2\n * d2\n * "
// );
// let tag = tags.next().unwrap();
// assert_eq!(tag.span.source_text(semantic.source_text), "@k3 d3\n * ");
// let tag = tags.next().unwrap();
// assert_eq!(
// tag.span.source_text(semantic.source_text),
// "@k4 d4\n * d4\n "
// );
// }
// }
#[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) -> Semantic<'a> {
let source_type = SourceType::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 jsdoc_span() {
let allocator = Allocator::default();
let semantic = build_semantic(
&allocator,
r"
/** single line */
/**
* multi
* line
*/
/**
multi
line
*/
",
);
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 jsdoc_comment() {
let allocator = Allocator::default();
let semantic = build_semantic(
&allocator,
r"
/** single line @k1 c1 @k2 */
/**
* multi
* line
* @k1 c1a
* c1b
* @k2 c2
* @k3 c3a
* c3b
*/
/** * list */
",
);
let mut jsdocs = semantic.jsdoc().iter_all();

let jsdoc = jsdocs.next().unwrap();
let comment = jsdoc.comment();
assert_eq!(comment.parsed(), "single line");
assert_eq!(comment.span.source_text(semantic.source_text), " single line ");

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

let jsdoc = jsdocs.next().unwrap();
let comment = jsdoc.comment();
assert_eq!(comment.parsed(), "* list");
assert_eq!(
comment.span.source_text(semantic.source_text),
" * list "
);
}

#[test]
fn jsdoc_tags() {
let allocator = Allocator::default();
let semantic = build_semantic(
&allocator,
r"
/** single line @k1 c1 @k2 */
/**
* multi
* line
* @k1 c1a
* c1b
* @k2 c2
* @k3 c3a
* c3b
*/
",
);
let mut jsdocs = semantic.jsdoc().iter_all();

let jsdoc = jsdocs.next().unwrap();
assert_eq!(jsdoc.tags().len(), 2);

let jsdoc = jsdocs.next().unwrap();
assert_eq!(jsdoc.tags().len(), 3);
}
}
55 changes: 35 additions & 20 deletions crates/oxc_semantic/src/jsdoc/parser/jsdoc_parts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,44 @@ impl<'a> JSDocCommentPart<'a> {
Self { raw: part_content, span }
}

// TODO: single line span?
// TODO: trimmed span?
// Use `Span` for `@kind` part instead of whole tag.
//
// For example, whole `tag.span` in the following JSDoc will be:
// For example, `comment_part.span` in the following JSDoc will be:
// ```
// /**
// * @kind1 bar
// * baz...
// * @kind2
// */
// for `@kind1`: `@kind1 bar\n * baz...\n * `
// for `@kind2`: `@kind2\n `
// ```
// for `@kind1`: `bar\n * baz...\n * `
// for `@kind2`: `\n `
//
// If passed `Span` for Miette's `Diagnostic` is single line,
// it will render nice underline for the span range.
// ```
// * @kind bar... @kind2
// ------
// ````
//
// But if the span is multiline, it will just render arrow mark at the start of each line.
// ```
// ╭─▶ * @kind1 bar
// ╰─▶ * @kind2
// ```
//
// It's too verbose and may not fit for linter diagnostics span.
// It's too verbose and may not fit for linter diagnostics.
// So instead, provide the first line span to indicate the span range.
pub fn span_first_line(&self) -> Span {
if let Some(first_line_end) = self.raw.find('\n') {
// -1 for `\n`
let span_end = first_line_end.checked_sub(1).unwrap_or_default();
return Span::new(
self.span.start,
self.span.start + u32::try_from(span_end).unwrap_or_default(),
);
}

self.span
}

pub fn parsed(&self) -> String {
let lines = self.raw.lines();
Expand All @@ -43,6 +67,7 @@ impl<'a> JSDocCommentPart<'a> {
}
}

/// `kind` can be any string like `param`, `type`, `whatever`, ...etc.
#[derive(Debug, Clone, Copy)]
pub struct JSDocTagKindPart<'a> {
raw: &'a str,
Expand All @@ -67,15 +92,10 @@ pub struct JSDocTagTypePart<'a> {
pub span: Span,
}
impl<'a> JSDocTagTypePart<'a> {
pub fn new(part_content: &'a str, span_start: u32) -> Self {
pub fn new(part_content: &'a str, span: Span) -> Self {
debug_assert!(part_content.starts_with('{'));
debug_assert!(part_content.ends_with('}'));

let span = Span::new(
span_start,
span_start + u32::try_from(part_content.len()).unwrap_or_default(),
);

Self { raw: part_content, span }
}

Expand All @@ -91,12 +111,7 @@ pub struct JSDocTagTypeNamePart<'a> {
pub span: Span,
}
impl<'a> JSDocTagTypeNamePart<'a> {
pub fn new(part_content: &'a str, span_start: u32) -> Self {
let span = Span::new(
span_start,
span_start + u32::try_from(part_content.len()).unwrap_or_default(),
);

pub fn new(part_content: &'a str, span: Span) -> Self {
Self { raw: part_content, span }
}

Expand Down
Loading