feat(ast)!: change comment.span to real position that contain // and /* (#7154)

closes #7150
This commit is contained in:
Boshen 2024-11-06 05:10:33 +00:00
parent 19892ede40
commit d1d187417b
27 changed files with 132 additions and 168 deletions

View file

@ -43,7 +43,7 @@ pub enum CommentPosition {
#[generate_derive(CloneIn, ContentEq, ContentHash)]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
pub struct Comment {
/// The span of the comment text (without leading/trailing delimiters).
/// The span of the comment text, with leading and trailing delimiters.
pub span: Span,
/// Start of token this leading comment is attached to.
@ -101,29 +101,12 @@ impl Comment {
self.position == CommentPosition::Trailing
}
#[allow(missing_docs)]
pub fn real_span(&self) -> Span {
Span::new(self.real_span_start(), self.real_span_end())
}
#[allow(missing_docs)]
pub fn real_span_end(&self) -> u32 {
match self.kind {
CommentKind::Line => self.span.end,
// length of `*/`
CommentKind::Block => self.span.end + 2,
}
}
#[allow(missing_docs)]
pub fn real_span_start(&self) -> u32 {
self.span.start - 2
}
/// Returns `true` if this comment is a JSDoc comment. Implies `is_leading`
/// and `is_block`.
pub fn is_jsdoc(&self, source_text: &str) -> bool {
self.is_leading() && self.is_block() && self.span.source_text(source_text).starts_with('*')
self.is_leading()
&& self.is_block()
&& self.content_span().source_text(source_text).starts_with('*')
}
/// Legal comments
@ -135,9 +118,17 @@ impl Comment {
if !self.is_leading() {
return false;
}
let source_text = self.span.source_text(source_text);
let source_text = self.content_span().source_text(source_text);
source_text.starts_with('!')
|| source_text.contains("@license")
|| source_text.contains("@preserve")
}
/// Gets the span of the comment content.
pub fn content_span(&self) -> Span {
match self.kind {
CommentKind::Line => Span::new(self.span.start + 2, self.span.end),
CommentKind::Block => Span::new(self.span.start + 2, self.span.end - 2),
}
}
}

View file

@ -41,7 +41,7 @@ impl<'a> Codegen<'a> {
///
/// <https://github.com/javascript-compiler-hints/compiler-notations-spec/blob/main/pure-notation-spec.md>
fn is_annotation_comment(&self, comment: &Comment) -> bool {
let s = comment.span.source_text(self.source_text).trim_start();
let s = comment.content_span().source_text(self.source_text).trim_start();
if let Some(s) = s.strip_prefix(['@', '#']) {
s.starts_with("__PURE__") || s.starts_with("__NO_SIDE_EFFECTS__")
} else {
@ -54,7 +54,7 @@ impl<'a> Codegen<'a> {
comment.preceded_by_newline
&& (comment.is_jsdoc(self.source_text)
|| (comment.is_line() && self.is_annotation_comment(comment)))
&& !comment.span.source_text(self.source_text).chars().all(|c| c == '*')
&& !comment.content_span().source_text(self.source_text).chars().all(|c| c == '*')
// webpack comment `/*****/`
}
@ -126,10 +126,10 @@ impl<'a> Codegen<'a> {
}
if comment.is_line() {
self.print_str("/*");
self.print_str(comment.span.source_text(self.source_text));
self.print_str(comment.content_span().source_text(self.source_text));
self.print_str("*/");
} else {
self.print_str(comment.real_span().source_text(self.source_text));
self.print_str(comment.span.source_text(self.source_text));
}
self.print_hard_space();
}
@ -205,7 +205,7 @@ impl<'a> Codegen<'a> {
}
fn print_comment(&mut self, comment: &Comment) {
let comment_source = comment.real_span().source_text(self.source_text);
let comment_source = comment.span.source_text(self.source_text);
match comment.kind {
CommentKind::Line => {
self.print_str(comment_source);

View file

@ -46,6 +46,6 @@ fn legal_external_comment() {
let code = "/* @license */\n/* @preserve */\nfoo;\n";
let ret = codegen_options(code, &options);
assert_eq!(ret.code, "foo;\n");
assert_eq!(ret.legal_comments[0].span.source_text(code), " @license ");
assert_eq!(ret.legal_comments[1].span.source_text(code), " @preserve ");
assert_eq!(ret.legal_comments[0].content_span().source_text(code), " @license ");
assert_eq!(ret.legal_comments[1].content_span().source_text(code), " @preserve ");
}

View file

@ -111,7 +111,8 @@ impl<'a> IsolatedDeclarations<'a> {
fn build_internal_annotations(program: &Program<'a>) -> FxHashSet<u32> {
let mut set = FxHashSet::default();
for comment in &program.comments {
let has_internal = comment.span.source_text(program.source_text).contains("@internal");
let has_internal =
comment.content_span().source_text(program.source_text).contains("@internal");
// Use the first jsdoc comment if there are multiple jsdoc comments for the same node.
if has_internal && !set.contains(&comment.attached_to) {
set.insert(comment.attached_to);

View file

@ -91,7 +91,8 @@ impl<'a> DisableDirectivesBuilder<'a> {
// for matching disable and enable pairs.
// Wrongly ordered matching pairs are not taken into consideration.
for comment in comments {
let text = comment.span.source_text(source_text);
let span = comment.content_span();
let text = span.source_text(source_text);
let text = text.trim_start();
if let Some(text) =
@ -100,50 +101,45 @@ impl<'a> DisableDirectivesBuilder<'a> {
// `eslint-disable`
if text.trim().is_empty() {
if self.disable_all_start.is_none() {
self.disable_all_start = Some(comment.span.end);
self.disable_all_start = Some(span.end);
}
self.disable_all_comments.push(comment.span);
self.disable_all_comments.push(span);
continue;
}
// `eslint-disable-next-line`
else if let Some(text) = text.strip_prefix("-next-line") {
// Get the span up to the next new line
let stop = source_text[comment.span.end as usize..]
let stop = source_text[span.end as usize..]
.lines()
.take(2)
.fold(comment.span.end, |acc, line| acc + line.len() as u32);
.fold(span.end, |acc, line| acc + line.len() as u32);
if text.trim().is_empty() {
self.add_interval(comment.span.end, stop, DisabledRule::All);
self.disable_all_comments.push(comment.span);
self.add_interval(span.end, stop, DisabledRule::All);
self.disable_all_comments.push(span);
} else {
// `eslint-disable-next-line rule_name1, rule_name2`
let mut rules = vec![];
Self::get_rule_names(text, |rule_name| {
self.add_interval(
comment.span.end,
stop,
DisabledRule::Single(rule_name),
);
self.add_interval(span.end, stop, DisabledRule::Single(rule_name));
rules.push(rule_name);
});
self.disable_rule_comments
.push(DisableRuleComment { span: comment.span, rules });
self.disable_rule_comments.push(DisableRuleComment { span, rules });
}
continue;
}
// `eslint-disable-line`
else if let Some(text) = text.strip_prefix("-line") {
// Get the span between the preceding newline to this comment
let start = source_text[..comment.span.start as usize]
let start = source_text[..span.start as usize]
.lines()
.next_back()
.map_or(0, |line| comment.span.start - line.len() as u32);
let stop = comment.span.start;
.map_or(0, |line| span.start - line.len() as u32);
let stop = span.start;
// `eslint-disable-line`
if text.trim().is_empty() {
self.add_interval(start, stop, DisabledRule::All);
self.disable_all_comments.push(comment.span);
self.disable_all_comments.push(span);
} else {
// `eslint-disable-line rule-name1, rule-name2`
let mut rules = vec![];
@ -151,8 +147,7 @@ impl<'a> DisableDirectivesBuilder<'a> {
self.add_interval(start, stop, DisabledRule::Single(rule_name));
rules.push(rule_name);
});
self.disable_rule_comments
.push(DisableRuleComment { span: comment.span, rules });
self.disable_rule_comments.push(DisableRuleComment { span, rules });
}
continue;
}
@ -162,11 +157,10 @@ impl<'a> DisableDirectivesBuilder<'a> {
// `eslint-disable rule-name1, rule-name2`
let mut rules = vec![];
Self::get_rule_names(text, |rule_name| {
self.disable_start_map.entry(rule_name).or_insert(comment.span.end);
self.disable_start_map.entry(rule_name).or_insert(span.end);
rules.push(rule_name);
});
self.disable_rule_comments
.push(DisableRuleComment { span: comment.span, rules });
self.disable_rule_comments.push(DisableRuleComment { span, rules });
continue;
}
}
@ -177,17 +171,13 @@ impl<'a> DisableDirectivesBuilder<'a> {
// `eslint-enable`
if text.trim().is_empty() {
if let Some(start) = self.disable_all_start.take() {
self.add_interval(start, comment.span.start, DisabledRule::All);
self.add_interval(start, span.start, DisabledRule::All);
}
} else {
// `eslint-enable rule-name1, rule-name2`
Self::get_rule_names(text, |rule_name| {
if let Some(start) = self.disable_start_map.remove(rule_name) {
self.add_interval(
start,
comment.span.start,
DisabledRule::Single(rule_name),
);
self.add_interval(start, span.start, DisabledRule::Single(rule_name));
}
});
}

View file

@ -79,7 +79,7 @@ impl Rule for DefaultCase {
.comments_range(last_case.span.start..switch.span.end)
.last()
.is_some_and(|comment| {
let raw = comment.span.source_text(ctx.semantic().source_text()).trim();
let raw = comment.content_span().source_text(ctx.source_text()).trim();
match &self.comment_pattern {
Some(comment_pattern) => comment_pattern.is_match(raw),
None => raw.eq_ignore_ascii_case("no default"),

View file

@ -83,8 +83,9 @@ impl Rule for MaxLines {
let comment_lines = if self.skip_comments {
let mut comment_lines: usize = 0;
for comment in ctx.semantic().comments() {
let comment_span = comment.content_span();
if comment.is_line() {
let comment_line = ctx.source_text()[..comment.span.start as usize]
let comment_line = ctx.source_text()[..comment_span.start as usize]
.lines()
.next_back()
.unwrap_or("");
@ -93,8 +94,8 @@ impl Rule for MaxLines {
}
} else {
let mut start_line =
ctx.source_text()[..comment.span.start as usize].lines().count();
let comment_start_line = ctx.source_text()[..comment.span.start as usize]
ctx.source_text()[..comment_span.start as usize].lines().count();
let comment_start_line = ctx.source_text()[..comment_span.start as usize]
.lines()
.next_back()
.unwrap_or("");
@ -102,9 +103,9 @@ impl Rule for MaxLines {
start_line += 1;
}
let mut end_line =
ctx.source_text()[..=comment.span.end as usize].lines().count();
ctx.source_text()[..=comment_span.end as usize].lines().count();
let comment_end_line =
ctx.source_text()[comment.span.end as usize..].lines().next().unwrap_or("");
ctx.source_text()[comment_span.end as usize..].lines().next().unwrap_or("");
if line_has_just_comment(comment_end_line, "*/") {
end_line += 1;
}

View file

@ -367,7 +367,7 @@ fn possible_fallthrough_comment_span(case: &SwitchCase) -> (u32, Option<u32>) {
impl NoFallthrough {
fn has_blanks_between(ctx: &LintContext, range: Range<u32>) -> bool {
let in_between = &ctx.semantic().source_text()[range.start as usize..range.end as usize];
let in_between = &ctx.source_text()[range.start as usize..range.end as usize];
// check for at least 2 new lines, we allow the first new line for formatting.
in_between.bytes().filter(|it| *it == b'\n').nth(1).is_some()
}
@ -382,9 +382,7 @@ impl NoFallthrough {
let is_fallthrough_comment_in_range = |range: Range<u32>| {
let comment = semantic
.comments_range(range)
.map(|comment| {
&semantic.source_text()[comment.span.start as usize..comment.span.end as usize]
})
.map(|comment| comment.content_span().source_text(semantic.source_text()))
.last()
.map(str::trim);

View file

@ -137,7 +137,7 @@ impl Rule for SortKeys {
let mut property_groups: Vec<Vec<String>> = vec![vec![]];
let source_text = ctx.semantic().source_text();
let source_text = ctx.source_text();
for (i, prop) in dec.properties.iter().enumerate() {
match prop {

View file

@ -60,16 +60,15 @@ impl Rule for NoCommentedOutTests {
Regex::new(r#"(?mu)^\s*[xf]?(test|it|describe)(\.\w+|\[['"]\w+['"]\])?\s*\("#).unwrap();
}
let comments = ctx.semantic().comments();
let source_text = ctx.semantic().source_text();
let source_text = ctx.source_text();
let commented_tests = comments.iter().filter_map(|comment| {
let text = comment.span.source_text(source_text);
let text = comment.content_span().source_text(source_text);
if RE.is_match(text) {
Some(comment.span)
Some(comment.content_span())
} else {
None
}
});
for span in commented_tests {
ctx.diagnostic(no_commented_out_tests_diagnostic(span));
}

View file

@ -157,7 +157,7 @@ impl Rule for BanTsComment {
fn run_once(&self, ctx: &LintContext) {
let comments = ctx.semantic().comments();
for comm in comments {
let raw = ctx.source_range(comm.span);
let raw = ctx.source_range(comm.content_span());
if let Some(captures) = find_ts_comment_directive(raw, comm.is_line()) {
// safe to unwrap, if capture success, it can always capture one of the four directives
let (directive, description) = (captures.0, captures.1);
@ -178,16 +178,16 @@ impl Rule for BanTsComment {
if *on {
if directive == "ignore" {
ctx.diagnostic_with_fix(
ignore_instead_of_expect_error(comm.span),
ignore_instead_of_expect_error(comm.content_span()),
|fixer| {
fixer.replace(
comm.span,
comm.content_span(),
raw.cow_replace("@ts-ignore", "@ts-expect-error"),
)
},
);
} else {
ctx.diagnostic(comment(directive, comm.span));
ctx.diagnostic(comment(directive, comm.content_span()));
}
}
}
@ -197,7 +197,7 @@ impl Rule for BanTsComment {
ctx.diagnostic(comment_requires_description(
directive,
self.minimum_description_length,
comm.span,
comm.content_span(),
));
}
@ -206,7 +206,7 @@ impl Rule for BanTsComment {
ctx.diagnostic(comment_description_not_match_pattern(
directive,
re.as_str(),
comm.span,
comm.content_span(),
));
}
}

View file

@ -35,18 +35,10 @@ impl Rule for BanTslintComment {
fn run_once(&self, ctx: &LintContext) {
let comments = ctx.semantic().comments();
let source_text_len = ctx.semantic().source_text().len();
for comment in comments {
let raw = comment.span.source_text(ctx.semantic().source_text());
let raw = comment.content_span().source_text(ctx.source_text());
if is_tslint_comment_directive(raw) {
let comment_span = get_full_comment(
source_text_len,
comment.span.start,
comment.span.end,
comment.is_block(),
);
let comment_span = get_full_comment(source_text_len, comment.span);
ctx.diagnostic_with_fix(
ban_tslint_comment_diagnostic(raw.trim(), comment_span),
|fixer| fixer.delete_range(comment_span),
@ -65,16 +57,13 @@ fn is_tslint_comment_directive(raw: &str) -> bool {
ENABLE_DISABLE_REGEX.is_match(raw)
}
fn get_full_comment(source_text_len: usize, start: u32, end: u32, is_multi_line: bool) -> Span {
let comment_start = start - 2;
let mut comment_end = if is_multi_line { end + 2 } else { end };
fn get_full_comment(source_text_len: usize, span: Span) -> Span {
let mut span = span;
// Take into account new line at the end of the comment
if source_text_len > comment_end as usize {
comment_end += 1;
if source_text_len > span.end as usize {
span.end += 1;
}
Span::new(comment_start, comment_end)
span
}
#[test]

View file

@ -166,13 +166,12 @@ fn check_member(member: &TSSignature, node: &AstNode<'_>, ctx: &LintContext<'_>)
let comments = ctx
.semantic()
.comments_range(node_start..node_end)
.map(|comment| (*comment, comment.span));
.map(|comment| (*comment, comment.content_span()));
let comments_text = {
let mut comments_vec: Vec<String> = vec![];
comments.for_each(|(comment_interface, span)| {
let comment = &source_code
[span.start as usize..span.end as usize];
let comment = span.source_text(source_code);
match comment_interface.kind {
CommentKind::Line => {

View file

@ -51,14 +51,14 @@ impl Rule for PreferTsExpectError {
let comments = ctx.semantic().comments();
for comment in comments {
let raw = comment.span.source_text(ctx.semantic().source_text());
let raw = comment.content_span().source_text(ctx.source_text());
if !is_valid_ts_ignore_present(*comment, raw) {
continue;
}
if comment.is_line() {
let comment_span = Span::new(comment.span.start - 2, comment.span.end);
let comment_span = comment.span;
ctx.diagnostic_with_fix(prefer_ts_expect_error_diagnostic(comment_span), |fixer| {
fixer.replace(
comment_span,
@ -66,7 +66,7 @@ impl Rule for PreferTsExpectError {
)
});
} else {
let comment_span = Span::new(comment.span.start - 2, comment.span.end + 2);
let comment_span = comment.span;
ctx.diagnostic_with_fix(prefer_ts_expect_error_diagnostic(comment_span), |fixer| {
fixer.replace(
comment_span,

View file

@ -115,22 +115,17 @@ impl Rule for TripleSlashReference {
let mut refs_for_import = FxHashMap::default();
for comment in ctx.semantic().comments_range(0..comments_range_end) {
let raw = &ctx.semantic().source_text()
[comment.span.start as usize..comment.span.end as usize];
let raw = comment.content_span().source_text(ctx.source_text());
if let Some((group1, group2)) = get_attr_key_and_value(raw) {
if (group1 == "types" && self.types == TypesOption::Never)
|| (group1 == "path" && self.path == PathOption::Never)
|| (group1 == "lib" && self.lib == LibOption::Never)
{
ctx.diagnostic(triple_slash_reference_diagnostic(
&group2,
Span::new(comment.span.start - 2, comment.span.end),
));
ctx.diagnostic(triple_slash_reference_diagnostic(&group2, comment.span));
}
if group1 == "types" && self.types == TypesOption::PreferImport {
refs_for_import
.insert(group2, Span::new(comment.span.start - 2, comment.span.end));
refs_for_import.insert(group2, comment.span);
}
}
}

View file

@ -73,7 +73,7 @@ fn has_triple_slash_directive(ctx: &LintContext<'_>) -> bool {
if !comment.is_line() {
continue;
}
let text = comment.span.source_text(ctx.source_text());
let text = comment.content_span().source_text(ctx.source_text());
if text.starts_with("///") {
return true;
}

View file

@ -93,7 +93,7 @@ fn get_call_expression_parentheses_pos<'a>(
let callee_span = member_expr.object().span();
// walk forward from the end of callee_span to find the opening `(` of the argument
let source = ctx.semantic().source_text().char_indices();
let source = ctx.source_text().char_indices();
let start = source
.skip(callee_span.end as usize)

View file

@ -187,7 +187,7 @@ impl Rule for NoUselessUndefined {
.comments_range(ret_stmt.span.start..ret_stmt.span.end)
.last()
{
Span::new(comment.span.end + 2, undefined_literal.span.end)
Span::new(comment.span.end, undefined_literal.span.end)
} else {
Span::new(ret_stmt.span().start + 6, undefined_literal.span.end)
};

View file

@ -226,8 +226,7 @@ pub fn has_pure_notation(span: Span, ctx: &LintContext) -> bool {
let Some(comment) = ctx.semantic().comments_range(..span.start).next_back() else {
return false;
};
let raw = comment.span.source_text(ctx.semantic().source_text());
let raw = comment.content_span().source_text(ctx.source_text());
raw.contains("@__PURE__") || raw.contains("#__PURE__")
}
@ -265,7 +264,8 @@ pub fn has_comment_about_side_effect_check(span: Span, ctx: &LintContext) -> boo
pub fn get_leading_tree_shaking_comment<'a>(span: Span, ctx: &LintContext<'a>) -> Option<&'a str> {
let comment = ctx.semantic().comments_range(..span.start).next_back()?;
let comment_text = comment.span.source_text(ctx.source_text());
let comment_span = comment.content_span();
let comment_text = comment_span.source_text(ctx.source_text());
if !is_tree_shaking_comment(comment_text) {
return None;
@ -273,7 +273,7 @@ pub fn get_leading_tree_shaking_comment<'a>(span: Span, ctx: &LintContext<'a>) -
// If there are non-whitespace characters between the `comment`` and the `span`,
// we treat the `comment` not belongs to the `span`.
let only_whitespace = ctx.source_text()[comment.span.end as usize..span.start as usize]
let only_whitespace = ctx.source_text()[comment_span.end as usize..span.start as usize]
.strip_prefix("*/") // for multi-line comment
.is_some_and(|s| s.trim().is_empty());
@ -294,9 +294,9 @@ pub fn get_leading_tree_shaking_comment<'a>(span: Span, ctx: &LintContext<'a>) -
return None;
};
if comment.span.end < current_line_start {
if comment_span.end < current_line_start {
let previous_line =
ctx.source_text()[..comment.span.end as usize].lines().next_back().unwrap_or("");
ctx.source_text()[..comment_span.end as usize].lines().next_back().unwrap_or("");
let nothing_before_comment = previous_line
.trim()
.strip_prefix(if comment.kind == CommentKind::Line { "//" } else { "/*" })

View file

@ -35,7 +35,7 @@ fn main() -> Result<(), String> {
if show_comments {
println!("Comments:");
for comment in ret.program.comments {
let s = comment.real_span().source_text(&source_text);
let s = comment.content_span().source_text(&source_text);
println!("{s}");
}
}

View file

@ -41,13 +41,11 @@ impl TriviaBuilder {
}
pub fn add_line_comment(&mut self, start: u32, end: u32) {
// skip leading `//`
self.add_comment(Comment::new(start + 2, end, CommentKind::Line));
self.add_comment(Comment::new(start, end, CommentKind::Line));
}
pub fn add_block_comment(&mut self, start: u32, end: u32) {
// skip leading `/*` and trailing `*/`
self.add_comment(Comment::new(start + 2, end - 2, CommentKind::Block));
self.add_comment(Comment::new(start, end, CommentKind::Block));
}
// For block comments only. This function is not called after line comments because the lexer skips
@ -157,7 +155,7 @@ mod test {
let comments = get_comments(source_text);
let expected = [
Comment {
span: Span::new(11, 22),
span: Span::new(9, 24),
kind: CommentKind::Block,
position: CommentPosition::Leading,
attached_to: 70,
@ -165,7 +163,7 @@ mod test {
followed_by_newline: true,
},
Comment {
span: Span::new(35, 45),
span: Span::new(33, 45),
kind: CommentKind::Line,
position: CommentPosition::Leading,
attached_to: 70,
@ -173,7 +171,7 @@ mod test {
followed_by_newline: true,
},
Comment {
span: Span::new(56, 67),
span: Span::new(54, 69),
kind: CommentKind::Block,
position: CommentPosition::Leading,
attached_to: 70,
@ -181,7 +179,7 @@ mod test {
followed_by_newline: false,
},
Comment {
span: Span::new(78, 90),
span: Span::new(76, 92),
kind: CommentKind::Block,
position: CommentPosition::Trailing,
attached_to: 0,
@ -189,7 +187,7 @@ mod test {
followed_by_newline: false,
},
Comment {
span: Span::new(95, 106),
span: Span::new(93, 106),
kind: CommentKind::Line,
position: CommentPosition::Trailing,
attached_to: 0,
@ -197,7 +195,7 @@ mod test {
followed_by_newline: true,
},
Comment {
span: Span::new(117, 138),
span: Span::new(115, 138),
kind: CommentKind::Line,
position: CommentPosition::Leading,
attached_to: 147,
@ -208,7 +206,7 @@ mod test {
assert_eq!(comments.len(), expected.len());
for (comment, expected) in comments.iter().copied().zip(expected) {
assert_eq!(comment, expected, "{}", comment.real_span().source_text(source_text));
assert_eq!(comment, expected, "{}", comment.content_span().source_text(source_text));
}
}
@ -221,7 +219,7 @@ token /* Trailing 1 */
let comments = get_comments(source_text);
let expected = vec![
Comment {
span: Span::new(22, 33),
span: Span::new(20, 35),
kind: CommentKind::Block,
position: CommentPosition::Leading,
attached_to: 36,
@ -229,7 +227,7 @@ token /* Trailing 1 */
followed_by_newline: true,
},
Comment {
span: Span::new(44, 56),
span: Span::new(42, 58),
kind: CommentKind::Block,
position: CommentPosition::Trailing,
attached_to: 0,
@ -254,7 +252,7 @@ token /* Trailing 1 */
let comments = get_comments(source_text);
let expected = vec![
Comment {
span: Span::new(3, 12),
span: Span::new(1, 14),
kind: CommentKind::Block,
position: CommentPosition::Leading,
attached_to: 30,
@ -262,7 +260,7 @@ token /* Trailing 1 */
followed_by_newline: true,
},
Comment {
span: Span::new(17, 26),
span: Span::new(15, 28),
kind: CommentKind::Block,
position: CommentPosition::Leading,
attached_to: 30,
@ -285,7 +283,7 @@ token /* Trailing 1 */
let comments = get_comments(source_text);
let expected = vec![
Comment {
span: Span::new(26, 44),
span: Span::new(24, 44),
kind: CommentKind::Line,
position: CommentPosition::Leading,
attached_to: 57,
@ -293,7 +291,7 @@ token /* Trailing 1 */
followed_by_newline: true,
},
Comment {
span: Span::new(98, 116),
span: Span::new(96, 116),
kind: CommentKind::Line,
position: CommentPosition::Leading,
attached_to: 129,
@ -315,7 +313,7 @@ token /* Trailing 1 */
let comments = get_comments(source_text);
let expected = vec![
Comment {
span: Span::new(20, 38),
span: Span::new(18, 38),
kind: CommentKind::Line,
position: CommentPosition::Leading,
attached_to: 55,
@ -323,7 +321,7 @@ token /* Trailing 1 */
followed_by_newline: true,
},
Comment {
span: Span::new(81, 99),
span: Span::new(79, 99),
kind: CommentKind::Line,
position: CommentPosition::Leading,
attached_to: 116,

View file

@ -14,10 +14,9 @@ pub struct Comment {
impl Comment {
pub fn new(comment: oxc_ast::Comment) -> Self {
let span = comment.real_span();
Self {
start: span.start,
end: span.end,
start: comment.span.start,
end: comment.span.end,
is_block: comment.is_block(),
has_line_suffix: false,
}

View file

@ -119,8 +119,9 @@ impl<'a> JSDocBuilder<'a> {
}
fn parse_jsdoc_comment(comment: &Comment, source_text: &'a str) -> JSDoc<'a> {
let span = comment.content_span();
// Remove the very first `*`
let jsdoc_span = Span::new(comment.span.start + 1, comment.span.end);
let jsdoc_span = Span::new(span.start + 1, span.end);
let comment_content = jsdoc_span.source_text(source_text);
JSDoc::new(comment_content, jsdoc_span)
}

View file

@ -84,7 +84,8 @@ fn find_jsx_pragma<'a>(
// Strip whitespace and `*`s from start of comment, and find leading `@`.
// Slice from start of comment to end of file, not end of comment.
// This allows `find_at_sign` functions to search in chunks of 8 bytes without hitting end of string.
let comment_str = &source_text[comment.span.start as usize..];
let comment_span = comment.content_span();
let comment_str = &source_text[comment_span.start as usize..];
let comment_str = match comment.kind {
CommentKind::Line => find_at_sign_in_line_comment(comment_str)?,
CommentKind::Block => find_at_sign_in_block_comment(comment_str)?,
@ -103,11 +104,11 @@ fn find_jsx_pragma<'a>(
// Slice off after end of comment
let remainder_start = source_text.len() - remainder.len();
if remainder_start >= comment.span.end as usize {
if remainder_start >= comment_span.end as usize {
// Space was after end of comment
return None;
}
let len = comment.span.end as usize - remainder_start;
let len = comment_span.end as usize - remainder_start;
let remainder = &remainder[..len];
// Trim excess whitespace/line breaks from end
let remainder = trim_end(remainder);
@ -346,19 +347,16 @@ mod tests {
}
fn create_comment(comment_str: &str, before: &str, after: &str) -> (Comment, String) {
let (kind, end_bytes) = if comment_str.starts_with("//") {
(CommentKind::Line, 0)
let kind = if comment_str.starts_with("//") {
CommentKind::Line
} else {
assert!(comment_str.starts_with("/*") && comment_str.ends_with("*/"));
(CommentKind::Block, 2)
CommentKind::Block
};
let source_text = format!("{before}{comment_str}{after}");
#[expect(clippy::cast_possible_truncation)]
let span = Span::new(
(before.len() + 2) as u32,
(before.len() + comment_str.len() - end_bytes) as u32,
);
let span = Span::new(before.len() as u32, (before.len() + comment_str.len()) as u32);
let comment = Comment {
span,
kind,

View file

@ -401,7 +401,7 @@ impl Oxc {
CommentKind::Line => CommentType::Line,
CommentKind::Block => CommentType::Block,
},
value: comment.span.source_text(source_text).to_string(),
value: comment.content_span().source_text(source_text).to_string(),
start: comment.span.start,
end: comment.span.end,
})

View file

@ -81,7 +81,7 @@ fn parse_with_return<'a>(source_text: &'a str, options: &ParserOptions) -> Parse
CommentKind::Line => "Line",
CommentKind::Block => "Block",
},
value: comment.span.source_text(source_text).to_string(),
value: comment.content_span().source_text(source_text).to_string(),
start: comment.span.start,
end: comment.span.end,
})

View file

@ -1,21 +1,26 @@
import { assert, describe, it } from 'vitest';
import { describe, expect, it } from 'vitest';
import * as oxc from '../index.js';
describe('parse', () => {
const code = '/* comment */ foo';
it('matches output', () => {
it('matches output', async () => {
const ret = oxc.parseSync(code);
assert(ret.program.body.length == 1);
assert(ret.errors.length == 0);
assert(ret.comments.length == 1);
});
expect(ret.program.body.length).toBe(1);
expect(ret.errors.length).toBe(0);
expect(ret.comments.length).toBe(1);
it('matches output async', async () => {
const ret = await oxc.parseAsync(code);
assert(ret.program.body.length == 1);
assert(ret.errors.length == 0);
assert(ret.comments.length == 1);
const comment = ret.comments[0];
expect(comment).toEqual({
'type': 'Block',
'start': 0,
'end': 13,
'value': ' comment ',
});
expect(code.substring(comment.start, comment.end)).toBe('/*' + comment.value + '*/');
const ret2 = await oxc.parseAsync(code);
expect(ret).toEqual(ret2);
});
});