Skip to content

Commit

Permalink
Add rule and snapshot
Browse files Browse the repository at this point in the history
  • Loading branch information
alexttyip authored and Boshen committed Feb 24, 2024
1 parent dd5eaeb commit 164dd43
Show file tree
Hide file tree
Showing 2 changed files with 244 additions and 53 deletions.
228 changes: 175 additions & 53 deletions crates/oxc_linter/src/rules/typescript/prefer_ts_expect_error.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
use lazy_static::lazy_static;
use regex::Regex;

use oxc_ast::Comment;
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::{self, Error},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

use crate::{context::LintContext, rule::Rule, AstNode};
use crate::fixer::Fix;
use crate::{context::LintContext, rule::Rule};

#[derive(Debug, Error, Diagnostic)]
#[error("typescript-eslint(prefer-ts-expect-error):")]
#[diagnostic(severity(warning), help("Use " @ ts - expect - error" to ensure an error is actually being suppressed."))]
#[error(
"typescript-eslint(prefer-ts-expect-error): Enforce using `@ts-expect-error` over `@ts-ignore`"
)]
#[diagnostic(
severity(warning),
help("Use \"@ts-expect-error\" to ensure an error is actually being suppressed.")
)]
struct PreferTsExpectErrorDiagnostic(#[label] pub Span);

#[derive(Debug, Default, Clone)]
Expand Down Expand Up @@ -43,65 +53,177 @@ declare_oxc_lint!(
);

impl Rule for PreferTsExpectError {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {}
fn run_once(&self, ctx: &LintContext) {
let comments = ctx.semantic().trivias().comments();

for (start, &comment) in comments {
let raw = &ctx.semantic().source_text()[*start as usize..comment.end() as usize];

if !is_valid_ts_ignore_present(comment, raw) {
continue;
}

if comment.is_single_line() {
let comment_span = Span::new(*start - 2, comment.end());
ctx.diagnostic_with_fix(PreferTsExpectErrorDiagnostic(comment_span), || {
Fix::new(
format!("//{}", raw.replace("@ts-ignore", "@ts-expect-error")),
comment_span,
)
});
} else {
let comment_span = Span::new(*start - 2, comment.end() + 2);
ctx.diagnostic_with_fix(PreferTsExpectErrorDiagnostic(comment_span), || {
Fix::new(
format!("/*{}*/", raw.replace("@ts-ignore", "@ts-expect-error")),
comment_span,
)
});
}
}
}
}

fn get_last_comment_line(comment: Comment, raw: &str) -> String {
if comment.is_single_line() {
return String::from(raw);
}

return String::from(raw.lines().last().unwrap_or(raw));
}

fn is_valid_ts_ignore_present(comment: Comment, raw: &str) -> bool {
let line = get_last_comment_line(comment, raw);

if comment.is_single_line() {
test_single_line_comment(&line)
} else {
test_multi_line_comment(&line)
}
}

fn test_single_line_comment(line: &str) -> bool {
lazy_static! {
static ref TS_IGNORE_SINGLE_LINE_REGEX: Regex = Regex::new(r"^\s*/?\s*@ts-ignore").unwrap();
}

TS_IGNORE_SINGLE_LINE_REGEX.is_match(line)
}

fn test_multi_line_comment(line: &str) -> bool {
lazy_static! {
static ref TS_IGNORE_MULTI_LINE_REGEX: Regex =
Regex::new(r"^\s*(?:\/|\*)*\s*@ts-ignore").unwrap();
}

TS_IGNORE_MULTI_LINE_REGEX.is_match(line)
}

#[test]
fn test() {
use crate::tester::Tester;

let pass = vec![
r#"// @ts-nocheck"#,
r#"// @ts-check"#,
r#"// just a comment containing @ts-ignore somewhere"#,
r#"
{
/*
just a comment containing @ts-ignore somewhere in a block
*/
}
"#,
r#"// @ts-expect-error"#,
r#"
if (false) {
// @ts-expect-error: Unreachable code error
console.log('hello');
}
"#,
r#"
/**
* Explaining comment
*
* @ts-expect-error
*
* Not last line
* */
"#,
"// @ts-nocheck",
"// @ts-check",
"// just a comment containing @ts-ignore somewhere",
"
{
/*
just a comment containing @ts-ignore somewhere in a block
*/
}
",
"// @ts-expect-error",
"
if (false) {
// @ts-expect-error: Unreachable code error
console.log('hello');
}
",
"
/**
* Explaining comment
*
* @ts-expect-error
*
* Not last line
* */
",
];

let fail = vec![
r#"// @ts-ignore"#,
r#"// @ts-ignore: Suppress next line"#,
r#"///@ts-ignore: Suppress next line"#,
r#"
if (false) {
// @ts-ignore: Unreachable code error
console.log('hello');
}
"#,
r#"/* @ts-ignore */"#,
r#"
/**
* Explaining comment
*
* @ts-ignore */
"#,
r#"/* @ts-ignore in a single block */"#,
r#"
/*
// @ts-ignore in a block with single line comments */
"#,
"// @ts-ignore",
"// @ts-ignore: Suppress next line",
"///@ts-ignore: Suppress next line",
"
if (false) {
// @ts-ignore: Unreachable code error
console.log('hello');
}
",
"/* @ts-ignore */",
"
/**
* Explaining comment
*
* @ts-ignore */
",
"/* @ts-ignore in a single block */",
"
/*
// @ts-ignore in a block with single line comments */
",
];

let fix = vec![
("// @ts-ignore", "// @ts-expect-error", None),
("// @ts-ignore: Suppress next line", "// @ts-expect-error: Suppress next line", None),
("///@ts-ignore: Suppress next line", "///@ts-expect-error: Suppress next line", None),
(
"
if (false) {
// @ts-ignore: Unreachable code error
console.log('hello');
}
",
"
if (false) {
// @ts-expect-error: Unreachable code error
console.log('hello');
}
",
None,
),
("/* @ts-ignore */", "/* @ts-expect-error */", None),
(
"
/**
* Explaining comment
*
* @ts-ignore */
",
"
/**
* Explaining comment
*
* @ts-expect-error */
",
None,
),
("/* @ts-ignore in a single block */", "/* @ts-expect-error in a single block */", None),
(
"
/*
// @ts-ignore in a block with single line comments */
",
"
/*
// @ts-expect-error in a block with single line comments */
",
None,
),
];

Tester::new(PreferTsExpectError::NAME, pass, fail).test_and_snapshot();
Tester::new(PreferTsExpectError::NAME, pass, fail).expect_fix(fix).test_and_snapshot();
}
69 changes: 69 additions & 0 deletions crates/oxc_linter/src/snapshots/prefer_ts_expect_error.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
source: crates/oxc_linter/src/tester.rs
expression: prefer_ts_expect_error
---

typescript-eslint(prefer-ts-expect-error): Enforce using `@ts-expect-error` over `@ts-ignore`
╭─[prefer_ts_expect_error.tsx:1:1]
1// @ts-ignore
· ─────────────
╰────
help: Use "@ts-expect-error" to ensure an error is actually being suppressed.

typescript-eslint(prefer-ts-expect-error): Enforce using `@ts-expect-error` over `@ts-ignore`
╭─[prefer_ts_expect_error.tsx:1:1]
1// @ts-ignore: Suppress next line
· ─────────────────────────────────
╰────
help: Use "@ts-expect-error" to ensure an error is actually being suppressed.

typescript-eslint(prefer-ts-expect-error): Enforce using `@ts-expect-error` over `@ts-ignore`
╭─[prefer_ts_expect_error.tsx:1:1]
1///@ts-ignore: Suppress next line
· ─────────────────────────────────
╰────
help: Use "@ts-expect-error" to ensure an error is actually being suppressed.

typescript-eslint(prefer-ts-expect-error): Enforce using `@ts-expect-error` over `@ts-ignore`
╭─[prefer_ts_expect_error.tsx:3:1]
2if (false) {
3// @ts-ignore: Unreachable code error
· ─────────────────────────────────────
4console.log('hello');
╰────
help: Use "@ts-expect-error" to ensure an error is actually being suppressed.

typescript-eslint(prefer-ts-expect-error): Enforce using `@ts-expect-error` over `@ts-ignore`
╭─[prefer_ts_expect_error.tsx:1:1]
1/* @ts-ignore */
· ────────────────
╰────
help: Use "@ts-expect-error" to ensure an error is actually being suppressed.

typescript-eslint(prefer-ts-expect-error): Enforce using `@ts-expect-error` over `@ts-ignore`
╭─[prefer_ts_expect_error.tsx:2:1]
1
2 │ ╭─▶ /**
3 │ │ * Explaining comment
4 │ │ *
5 │ ╰─▶ * @ts-ignore */
6
╰────
help: Use "@ts-expect-error" to ensure an error is actually being suppressed.

typescript-eslint(prefer-ts-expect-error): Enforce using `@ts-expect-error` over `@ts-ignore`
╭─[prefer_ts_expect_error.tsx:1:1]
1/* @ts-ignore in a single block */
· ──────────────────────────────────
╰────
help: Use "@ts-expect-error" to ensure an error is actually being suppressed.

typescript-eslint(prefer-ts-expect-error): Enforce using `@ts-expect-error` over `@ts-ignore`
╭─[prefer_ts_expect_error.tsx:2:1]
1
2 │ ╭─▶ /*
3 │ ╰─▶ // @ts-ignore in a block with single line comments */
4
╰────
help: Use "@ts-expect-error" to ensure an error is actually being suppressed.

0 comments on commit 164dd43

Please sign in to comment.