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)] #[generate_derive(CloneIn, ContentEq, ContentHash)]
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
pub struct Comment { 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, pub span: Span,
/// Start of token this leading comment is attached to. /// Start of token this leading comment is attached to.
@ -101,29 +101,12 @@ impl Comment {
self.position == CommentPosition::Trailing 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` /// Returns `true` if this comment is a JSDoc comment. Implies `is_leading`
/// and `is_block`. /// and `is_block`.
pub fn is_jsdoc(&self, source_text: &str) -> bool { 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 /// Legal comments
@ -135,9 +118,17 @@ impl Comment {
if !self.is_leading() { if !self.is_leading() {
return false; 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.starts_with('!')
|| source_text.contains("@license") || source_text.contains("@license")
|| source_text.contains("@preserve") || 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> /// <https://github.com/javascript-compiler-hints/compiler-notations-spec/blob/main/pure-notation-spec.md>
fn is_annotation_comment(&self, comment: &Comment) -> bool { 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(['@', '#']) { if let Some(s) = s.strip_prefix(['@', '#']) {
s.starts_with("__PURE__") || s.starts_with("__NO_SIDE_EFFECTS__") s.starts_with("__PURE__") || s.starts_with("__NO_SIDE_EFFECTS__")
} else { } else {
@ -54,7 +54,7 @@ impl<'a> Codegen<'a> {
comment.preceded_by_newline comment.preceded_by_newline
&& (comment.is_jsdoc(self.source_text) && (comment.is_jsdoc(self.source_text)
|| (comment.is_line() && self.is_annotation_comment(comment))) || (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 `/*****/` // webpack comment `/*****/`
} }
@ -126,10 +126,10 @@ impl<'a> Codegen<'a> {
} }
if comment.is_line() { if comment.is_line() {
self.print_str("/*"); 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("*/"); self.print_str("*/");
} else { } 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(); self.print_hard_space();
} }
@ -205,7 +205,7 @@ impl<'a> Codegen<'a> {
} }
fn print_comment(&mut self, comment: &Comment) { 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 { match comment.kind {
CommentKind::Line => { CommentKind::Line => {
self.print_str(comment_source); self.print_str(comment_source);

View file

@ -46,6 +46,6 @@ fn legal_external_comment() {
let code = "/* @license */\n/* @preserve */\nfoo;\n"; let code = "/* @license */\n/* @preserve */\nfoo;\n";
let ret = codegen_options(code, &options); let ret = codegen_options(code, &options);
assert_eq!(ret.code, "foo;\n"); assert_eq!(ret.code, "foo;\n");
assert_eq!(ret.legal_comments[0].span.source_text(code), " @license "); assert_eq!(ret.legal_comments[0].content_span().source_text(code), " @license ");
assert_eq!(ret.legal_comments[1].span.source_text(code), " @preserve "); 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> { fn build_internal_annotations(program: &Program<'a>) -> FxHashSet<u32> {
let mut set = FxHashSet::default(); let mut set = FxHashSet::default();
for comment in &program.comments { 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. // Use the first jsdoc comment if there are multiple jsdoc comments for the same node.
if has_internal && !set.contains(&comment.attached_to) { if has_internal && !set.contains(&comment.attached_to) {
set.insert(comment.attached_to); set.insert(comment.attached_to);

View file

@ -91,7 +91,8 @@ impl<'a> DisableDirectivesBuilder<'a> {
// for matching disable and enable pairs. // for matching disable and enable pairs.
// Wrongly ordered matching pairs are not taken into consideration. // Wrongly ordered matching pairs are not taken into consideration.
for comment in comments { 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(); let text = text.trim_start();
if let Some(text) = if let Some(text) =
@ -100,50 +101,45 @@ impl<'a> DisableDirectivesBuilder<'a> {
// `eslint-disable` // `eslint-disable`
if text.trim().is_empty() { if text.trim().is_empty() {
if self.disable_all_start.is_none() { 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; continue;
} }
// `eslint-disable-next-line` // `eslint-disable-next-line`
else if let Some(text) = text.strip_prefix("-next-line") { else if let Some(text) = text.strip_prefix("-next-line") {
// Get the span up to the next new 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() .lines()
.take(2) .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() { if text.trim().is_empty() {
self.add_interval(comment.span.end, stop, DisabledRule::All); self.add_interval(span.end, stop, DisabledRule::All);
self.disable_all_comments.push(comment.span); self.disable_all_comments.push(span);
} else { } else {
// `eslint-disable-next-line rule_name1, rule_name2` // `eslint-disable-next-line rule_name1, rule_name2`
let mut rules = vec![]; let mut rules = vec![];
Self::get_rule_names(text, |rule_name| { Self::get_rule_names(text, |rule_name| {
self.add_interval( self.add_interval(span.end, stop, DisabledRule::Single(rule_name));
comment.span.end,
stop,
DisabledRule::Single(rule_name),
);
rules.push(rule_name); rules.push(rule_name);
}); });
self.disable_rule_comments self.disable_rule_comments.push(DisableRuleComment { span, rules });
.push(DisableRuleComment { span: comment.span, rules });
} }
continue; continue;
} }
// `eslint-disable-line` // `eslint-disable-line`
else if let Some(text) = text.strip_prefix("-line") { else if let Some(text) = text.strip_prefix("-line") {
// Get the span between the preceding newline to this comment // 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() .lines()
.next_back() .next_back()
.map_or(0, |line| comment.span.start - line.len() as u32); .map_or(0, |line| span.start - line.len() as u32);
let stop = comment.span.start; let stop = span.start;
// `eslint-disable-line` // `eslint-disable-line`
if text.trim().is_empty() { if text.trim().is_empty() {
self.add_interval(start, stop, DisabledRule::All); self.add_interval(start, stop, DisabledRule::All);
self.disable_all_comments.push(comment.span); self.disable_all_comments.push(span);
} else { } else {
// `eslint-disable-line rule-name1, rule-name2` // `eslint-disable-line rule-name1, rule-name2`
let mut rules = vec![]; let mut rules = vec![];
@ -151,8 +147,7 @@ impl<'a> DisableDirectivesBuilder<'a> {
self.add_interval(start, stop, DisabledRule::Single(rule_name)); self.add_interval(start, stop, DisabledRule::Single(rule_name));
rules.push(rule_name); rules.push(rule_name);
}); });
self.disable_rule_comments self.disable_rule_comments.push(DisableRuleComment { span, rules });
.push(DisableRuleComment { span: comment.span, rules });
} }
continue; continue;
} }
@ -162,11 +157,10 @@ impl<'a> DisableDirectivesBuilder<'a> {
// `eslint-disable rule-name1, rule-name2` // `eslint-disable rule-name1, rule-name2`
let mut rules = vec![]; let mut rules = vec![];
Self::get_rule_names(text, |rule_name| { 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); rules.push(rule_name);
}); });
self.disable_rule_comments self.disable_rule_comments.push(DisableRuleComment { span, rules });
.push(DisableRuleComment { span: comment.span, rules });
continue; continue;
} }
} }
@ -177,17 +171,13 @@ impl<'a> DisableDirectivesBuilder<'a> {
// `eslint-enable` // `eslint-enable`
if text.trim().is_empty() { if text.trim().is_empty() {
if let Some(start) = self.disable_all_start.take() { 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 { } else {
// `eslint-enable rule-name1, rule-name2` // `eslint-enable rule-name1, rule-name2`
Self::get_rule_names(text, |rule_name| { Self::get_rule_names(text, |rule_name| {
if let Some(start) = self.disable_start_map.remove(rule_name) { if let Some(start) = self.disable_start_map.remove(rule_name) {
self.add_interval( self.add_interval(start, span.start, DisabledRule::Single(rule_name));
start,
comment.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) .comments_range(last_case.span.start..switch.span.end)
.last() .last()
.is_some_and(|comment| { .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 { match &self.comment_pattern {
Some(comment_pattern) => comment_pattern.is_match(raw), Some(comment_pattern) => comment_pattern.is_match(raw),
None => raw.eq_ignore_ascii_case("no default"), 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 comment_lines = if self.skip_comments {
let mut comment_lines: usize = 0; let mut comment_lines: usize = 0;
for comment in ctx.semantic().comments() { for comment in ctx.semantic().comments() {
let comment_span = comment.content_span();
if comment.is_line() { 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() .lines()
.next_back() .next_back()
.unwrap_or(""); .unwrap_or("");
@ -93,8 +94,8 @@ impl Rule for MaxLines {
} }
} else { } else {
let mut start_line = let mut start_line =
ctx.source_text()[..comment.span.start as usize].lines().count(); ctx.source_text()[..comment_span.start as usize].lines().count();
let comment_start_line = ctx.source_text()[..comment.span.start as usize] let comment_start_line = ctx.source_text()[..comment_span.start as usize]
.lines() .lines()
.next_back() .next_back()
.unwrap_or(""); .unwrap_or("");
@ -102,9 +103,9 @@ impl Rule for MaxLines {
start_line += 1; start_line += 1;
} }
let mut end_line = 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 = 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, "*/") { if line_has_just_comment(comment_end_line, "*/") {
end_line += 1; end_line += 1;
} }

View file

@ -367,7 +367,7 @@ fn possible_fallthrough_comment_span(case: &SwitchCase) -> (u32, Option<u32>) {
impl NoFallthrough { impl NoFallthrough {
fn has_blanks_between(ctx: &LintContext, range: Range<u32>) -> bool { 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. // 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() 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 is_fallthrough_comment_in_range = |range: Range<u32>| {
let comment = semantic let comment = semantic
.comments_range(range) .comments_range(range)
.map(|comment| { .map(|comment| comment.content_span().source_text(semantic.source_text()))
&semantic.source_text()[comment.span.start as usize..comment.span.end as usize]
})
.last() .last()
.map(str::trim); .map(str::trim);

View file

@ -137,7 +137,7 @@ impl Rule for SortKeys {
let mut property_groups: Vec<Vec<String>> = vec![vec![]]; 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() { for (i, prop) in dec.properties.iter().enumerate() {
match prop { 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(); Regex::new(r#"(?mu)^\s*[xf]?(test|it|describe)(\.\w+|\[['"]\w+['"]\])?\s*\("#).unwrap();
} }
let comments = ctx.semantic().comments(); 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 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) { if RE.is_match(text) {
Some(comment.span) Some(comment.content_span())
} else { } else {
None None
} }
}); });
for span in commented_tests { for span in commented_tests {
ctx.diagnostic(no_commented_out_tests_diagnostic(span)); ctx.diagnostic(no_commented_out_tests_diagnostic(span));
} }

View file

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

View file

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

View file

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

View file

@ -51,14 +51,14 @@ impl Rule for PreferTsExpectError {
let comments = ctx.semantic().comments(); let comments = ctx.semantic().comments();
for comment in 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) { if !is_valid_ts_ignore_present(*comment, raw) {
continue; continue;
} }
if comment.is_line() { 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| { ctx.diagnostic_with_fix(prefer_ts_expect_error_diagnostic(comment_span), |fixer| {
fixer.replace( fixer.replace(
comment_span, comment_span,
@ -66,7 +66,7 @@ impl Rule for PreferTsExpectError {
) )
}); });
} else { } 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| { ctx.diagnostic_with_fix(prefer_ts_expect_error_diagnostic(comment_span), |fixer| {
fixer.replace( fixer.replace(
comment_span, comment_span,

View file

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

View file

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

View file

@ -93,7 +93,7 @@ fn get_call_expression_parentheses_pos<'a>(
let callee_span = member_expr.object().span(); let callee_span = member_expr.object().span();
// walk forward from the end of callee_span to find the opening `(` of the argument // 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 let start = source
.skip(callee_span.end as usize) .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) .comments_range(ret_stmt.span.start..ret_stmt.span.end)
.last() .last()
{ {
Span::new(comment.span.end + 2, undefined_literal.span.end) Span::new(comment.span.end, undefined_literal.span.end)
} else { } else {
Span::new(ret_stmt.span().start + 6, undefined_literal.span.end) 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 { let Some(comment) = ctx.semantic().comments_range(..span.start).next_back() else {
return false; 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__") 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> { 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 = 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) { if !is_tree_shaking_comment(comment_text) {
return None; 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`, // If there are non-whitespace characters between the `comment`` and the `span`,
// we treat the `comment` not belongs to 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 .strip_prefix("*/") // for multi-line comment
.is_some_and(|s| s.trim().is_empty()); .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; return None;
}; };
if comment.span.end < current_line_start { if comment_span.end < current_line_start {
let previous_line = 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 let nothing_before_comment = previous_line
.trim() .trim()
.strip_prefix(if comment.kind == CommentKind::Line { "//" } else { "/*" }) .strip_prefix(if comment.kind == CommentKind::Line { "//" } else { "/*" })

View file

@ -35,7 +35,7 @@ fn main() -> Result<(), String> {
if show_comments { if show_comments {
println!("Comments:"); println!("Comments:");
for comment in ret.program.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}"); println!("{s}");
} }
} }

View file

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

View file

@ -14,10 +14,9 @@ pub struct Comment {
impl Comment { impl Comment {
pub fn new(comment: oxc_ast::Comment) -> Self { pub fn new(comment: oxc_ast::Comment) -> Self {
let span = comment.real_span();
Self { Self {
start: span.start, start: comment.span.start,
end: span.end, end: comment.span.end,
is_block: comment.is_block(), is_block: comment.is_block(),
has_line_suffix: false, 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> { fn parse_jsdoc_comment(comment: &Comment, source_text: &'a str) -> JSDoc<'a> {
let span = comment.content_span();
// Remove the very first `*` // 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); let comment_content = jsdoc_span.source_text(source_text);
JSDoc::new(comment_content, jsdoc_span) 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 `@`. // Strip whitespace and `*`s from start of comment, and find leading `@`.
// Slice from start of comment to end of file, not end of comment. // 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. // 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 { let comment_str = match comment.kind {
CommentKind::Line => find_at_sign_in_line_comment(comment_str)?, CommentKind::Line => find_at_sign_in_line_comment(comment_str)?,
CommentKind::Block => find_at_sign_in_block_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 // Slice off after end of comment
let remainder_start = source_text.len() - remainder.len(); 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 // Space was after end of comment
return None; return None;
} }
let len = comment.span.end as usize - remainder_start; let len = comment_span.end as usize - remainder_start;
let remainder = &remainder[..len]; let remainder = &remainder[..len];
// Trim excess whitespace/line breaks from end // Trim excess whitespace/line breaks from end
let remainder = trim_end(remainder); let remainder = trim_end(remainder);
@ -346,19 +347,16 @@ mod tests {
} }
fn create_comment(comment_str: &str, before: &str, after: &str) -> (Comment, String) { fn create_comment(comment_str: &str, before: &str, after: &str) -> (Comment, String) {
let (kind, end_bytes) = if comment_str.starts_with("//") { let kind = if comment_str.starts_with("//") {
(CommentKind::Line, 0) CommentKind::Line
} else { } else {
assert!(comment_str.starts_with("/*") && comment_str.ends_with("*/")); assert!(comment_str.starts_with("/*") && comment_str.ends_with("*/"));
(CommentKind::Block, 2) CommentKind::Block
}; };
let source_text = format!("{before}{comment_str}{after}"); let source_text = format!("{before}{comment_str}{after}");
#[expect(clippy::cast_possible_truncation)] #[expect(clippy::cast_possible_truncation)]
let span = Span::new( let span = Span::new(before.len() as u32, (before.len() + comment_str.len()) as u32);
(before.len() + 2) as u32,
(before.len() + comment_str.len() - end_bytes) as u32,
);
let comment = Comment { let comment = Comment {
span, span,
kind, kind,

View file

@ -401,7 +401,7 @@ impl Oxc {
CommentKind::Line => CommentType::Line, CommentKind::Line => CommentType::Line,
CommentKind::Block => CommentType::Block, 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, start: comment.span.start,
end: comment.span.end, 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::Line => "Line",
CommentKind::Block => "Block", 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, start: comment.span.start,
end: comment.span.end, 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'; import * as oxc from '../index.js';
describe('parse', () => { describe('parse', () => {
const code = '/* comment */ foo'; const code = '/* comment */ foo';
it('matches output', () => { it('matches output', async () => {
const ret = oxc.parseSync(code); const ret = oxc.parseSync(code);
assert(ret.program.body.length == 1); expect(ret.program.body.length).toBe(1);
assert(ret.errors.length == 0); expect(ret.errors.length).toBe(0);
assert(ret.comments.length == 1); expect(ret.comments.length).toBe(1);
});
it('matches output async', async () => { const comment = ret.comments[0];
const ret = await oxc.parseAsync(code); expect(comment).toEqual({
assert(ret.program.body.length == 1); 'type': 'Block',
assert(ret.errors.length == 0); 'start': 0,
assert(ret.comments.length == 1); 'end': 13,
'value': ' comment ',
});
expect(code.substring(comment.start, comment.end)).toBe('/*' + comment.value + '*/');
const ret2 = await oxc.parseAsync(code);
expect(ret).toEqual(ret2);
}); });
}); });