diff --git a/crates/oxc_ast/src/ast/comment.rs b/crates/oxc_ast/src/ast/comment.rs index ec436eb584650..fa2f6b4fbda59 100644 --- a/crates/oxc_ast/src/ast/comment.rs +++ b/crates/oxc_ast/src/ast/comment.rs @@ -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), + } + } } diff --git a/crates/oxc_codegen/src/comment.rs b/crates/oxc_codegen/src/comment.rs index 2366067149d3b..8040a4384bb58 100644 --- a/crates/oxc_codegen/src/comment.rs +++ b/crates/oxc_codegen/src/comment.rs @@ -41,7 +41,7 @@ impl<'a> Codegen<'a> { /// /// 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); diff --git a/crates/oxc_codegen/tests/integration/legal_comments.rs b/crates/oxc_codegen/tests/integration/legal_comments.rs index d6a3887eb3d18..7ac57d2b4b5b0 100644 --- a/crates/oxc_codegen/tests/integration/legal_comments.rs +++ b/crates/oxc_codegen/tests/integration/legal_comments.rs @@ -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 "); } diff --git a/crates/oxc_isolated_declarations/src/lib.rs b/crates/oxc_isolated_declarations/src/lib.rs index b76023d8a3777..35a8db768fcd0 100644 --- a/crates/oxc_isolated_declarations/src/lib.rs +++ b/crates/oxc_isolated_declarations/src/lib.rs @@ -111,7 +111,8 @@ impl<'a> IsolatedDeclarations<'a> { fn build_internal_annotations(program: &Program<'a>) -> FxHashSet { 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); diff --git a/crates/oxc_linter/src/disable_directives.rs b/crates/oxc_linter/src/disable_directives.rs index 3f8ffe115b2f6..960bc490c0b46 100644 --- a/crates/oxc_linter/src/disable_directives.rs +++ b/crates/oxc_linter/src/disable_directives.rs @@ -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)); } }); } diff --git a/crates/oxc_linter/src/rules/eslint/default_case.rs b/crates/oxc_linter/src/rules/eslint/default_case.rs index 59977313b665b..240638a9220c3 100644 --- a/crates/oxc_linter/src/rules/eslint/default_case.rs +++ b/crates/oxc_linter/src/rules/eslint/default_case.rs @@ -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"), diff --git a/crates/oxc_linter/src/rules/eslint/max_lines.rs b/crates/oxc_linter/src/rules/eslint/max_lines.rs index 2311b346a411b..419f5961fd918 100644 --- a/crates/oxc_linter/src/rules/eslint/max_lines.rs +++ b/crates/oxc_linter/src/rules/eslint/max_lines.rs @@ -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; } diff --git a/crates/oxc_linter/src/rules/eslint/no_fallthrough.rs b/crates/oxc_linter/src/rules/eslint/no_fallthrough.rs index 4476fe38b9711..f58b023d983e8 100644 --- a/crates/oxc_linter/src/rules/eslint/no_fallthrough.rs +++ b/crates/oxc_linter/src/rules/eslint/no_fallthrough.rs @@ -367,7 +367,7 @@ fn possible_fallthrough_comment_span(case: &SwitchCase) -> (u32, Option) { impl NoFallthrough { fn has_blanks_between(ctx: &LintContext, range: Range) -> 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| { 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); diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index 76502d3198d3a..84339ba0ed75c 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -137,7 +137,7 @@ impl Rule for SortKeys { let mut property_groups: Vec> = 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 { diff --git a/crates/oxc_linter/src/rules/jest/no_commented_out_tests.rs b/crates/oxc_linter/src/rules/jest/no_commented_out_tests.rs index 089b017666a4e..ce283b6d43d5e 100644 --- a/crates/oxc_linter/src/rules/jest/no_commented_out_tests.rs +++ b/crates/oxc_linter/src/rules/jest/no_commented_out_tests.rs @@ -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)); } diff --git a/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs b/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs index cafcf4514b6c8..2cd20a8835ce3 100644 --- a/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs +++ b/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs @@ -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(), )); } } diff --git a/crates/oxc_linter/src/rules/typescript/ban_tslint_comment.rs b/crates/oxc_linter/src/rules/typescript/ban_tslint_comment.rs index 6f3a08da1d2d3..302887c65723f 100644 --- a/crates/oxc_linter/src/rules/typescript/ban_tslint_comment.rs +++ b/crates/oxc_linter/src/rules/typescript/ban_tslint_comment.rs @@ -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] diff --git a/crates/oxc_linter/src/rules/typescript/prefer_function_type.rs b/crates/oxc_linter/src/rules/typescript/prefer_function_type.rs index 82ad8493ab6bd..2d5e2a179f7ff 100644 --- a/crates/oxc_linter/src/rules/typescript/prefer_function_type.rs +++ b/crates/oxc_linter/src/rules/typescript/prefer_function_type.rs @@ -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 = 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 => { diff --git a/crates/oxc_linter/src/rules/typescript/prefer_ts_expect_error.rs b/crates/oxc_linter/src/rules/typescript/prefer_ts_expect_error.rs index d0a138798b47a..1b0c8144e0ada 100644 --- a/crates/oxc_linter/src/rules/typescript/prefer_ts_expect_error.rs +++ b/crates/oxc_linter/src/rules/typescript/prefer_ts_expect_error.rs @@ -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, diff --git a/crates/oxc_linter/src/rules/typescript/triple_slash_reference.rs b/crates/oxc_linter/src/rules/typescript/triple_slash_reference.rs index 2adab5bcb3f99..17d3f5699aeef 100644 --- a/crates/oxc_linter/src/rules/typescript/triple_slash_reference.rs +++ b/crates/oxc_linter/src/rules/typescript/triple_slash_reference.rs @@ -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); } } } diff --git a/crates/oxc_linter/src/rules/unicorn/no_empty_file.rs b/crates/oxc_linter/src/rules/unicorn/no_empty_file.rs index 37732d5cfc59c..e61ac0f486d0f 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_empty_file.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_empty_file.rs @@ -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; } diff --git a/crates/oxc_linter/src/rules/unicorn/no_magic_array_flat_depth.rs b/crates/oxc_linter/src/rules/unicorn/no_magic_array_flat_depth.rs index eccf93ac98b3b..c54cd26568945 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_magic_array_flat_depth.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_magic_array_flat_depth.rs @@ -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) diff --git a/crates/oxc_linter/src/rules/unicorn/no_useless_undefined.rs b/crates/oxc_linter/src/rules/unicorn/no_useless_undefined.rs index c5ea0e8b7f7f6..d7857d6b28c76 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_useless_undefined.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_useless_undefined.rs @@ -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) }; diff --git a/crates/oxc_linter/src/utils/tree_shaking.rs b/crates/oxc_linter/src/utils/tree_shaking.rs index 0dded6cdf9634..47cd03d91578c 100644 --- a/crates/oxc_linter/src/utils/tree_shaking.rs +++ b/crates/oxc_linter/src/utils/tree_shaking.rs @@ -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 { "/*" }) diff --git a/crates/oxc_parser/examples/parser.rs b/crates/oxc_parser/examples/parser.rs index 7e43ee125c94d..279fa3566ac30 100644 --- a/crates/oxc_parser/examples/parser.rs +++ b/crates/oxc_parser/examples/parser.rs @@ -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}"); } } diff --git a/crates/oxc_parser/src/lexer/trivia_builder.rs b/crates/oxc_parser/src/lexer/trivia_builder.rs index 513bef5752e0e..ec1df541d8494 100644 --- a/crates/oxc_parser/src/lexer/trivia_builder.rs +++ b/crates/oxc_parser/src/lexer/trivia_builder.rs @@ -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, diff --git a/crates/oxc_prettier/src/comments/mod.rs b/crates/oxc_prettier/src/comments/mod.rs index 6d702961e5338..6c23e43edb722 100644 --- a/crates/oxc_prettier/src/comments/mod.rs +++ b/crates/oxc_prettier/src/comments/mod.rs @@ -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, } diff --git a/crates/oxc_semantic/src/jsdoc/builder.rs b/crates/oxc_semantic/src/jsdoc/builder.rs index aa67622512f32..82be8c41bb8bb 100644 --- a/crates/oxc_semantic/src/jsdoc/builder.rs +++ b/crates/oxc_semantic/src/jsdoc/builder.rs @@ -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) } diff --git a/crates/oxc_transformer/src/jsx/comments.rs b/crates/oxc_transformer/src/jsx/comments.rs index 528cee1d0ece4..41cafc967813e 100644 --- a/crates/oxc_transformer/src/jsx/comments.rs +++ b/crates/oxc_transformer/src/jsx/comments.rs @@ -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, diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index df82c6f21921a..6840cc6c0b2ba 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -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, }) diff --git a/napi/parser/src/lib.rs b/napi/parser/src/lib.rs index 3f9efe1209460..08add6965e2fd 100644 --- a/napi/parser/src/lib.rs +++ b/napi/parser/src/lib.rs @@ -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, }) diff --git a/napi/parser/test/parse.test.mjs b/napi/parser/test/parse.test.mjs index 9d49df1aae13c..909094c321033 100644 --- a/napi/parser/test/parse.test.mjs +++ b/napi/parser/test/parse.test.mjs @@ -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); + + 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 + '*/'); - 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 ret2 = await oxc.parseAsync(code); + expect(ret).toEqual(ret2); }); });