mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(ast)!: change comment.span to real position that contain // and /* (#7154)
closes #7150
This commit is contained in:
parent
19892ede40
commit
d1d187417b
27 changed files with 132 additions and 168 deletions
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 ");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 { "/*" })
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue