Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(linter): rule noTsIgnore #4650

Merged
merged 8 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/add_the_new_rule_notsignorehttps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
cli: minor
---

# Add the new rule [`noTsIgnore`](https://biomejs.dev/linter/rules/no-ts-ignore)
14 changes: 13 additions & 1 deletion crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

127 changes: 74 additions & 53 deletions crates/biome_configuration/src/analyzer/linter/rules.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/biome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ define_categories! {
"lint/nursery/noStaticElementInteractions": "https://biomejs.dev/linter/rules/no-static-element-interactions",
"lint/nursery/noSubstr": "https://biomejs.dev/linter/rules/no-substr",
"lint/nursery/noTemplateCurlyInString": "https://biomejs.dev/linter/rules/no-template-curly-in-string",
"lint/nursery/noTsIgnore": "https://biomejs.dev/linter/rules/no-ts-ignore",
"lint/nursery/noUndeclaredDependencies": "https://biomejs.dev/linter/rules/no-undeclared-dependencies",
"lint/nursery/noUnknownFunction": "https://biomejs.dev/linter/rules/no-unknown-function",
"lint/nursery/noUnknownMediaFeatureName": "https://biomejs.dev/linter/rules/no-unknown-media-feature-name",
Expand Down
2 changes: 2 additions & 0 deletions crates/biome_js_analyze/src/lint/nursery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub mod no_secrets;
pub mod no_static_element_interactions;
pub mod no_substr;
pub mod no_template_curly_in_string;
pub mod no_ts_ignore;
pub mod no_useless_escape_in_regex;
pub mod no_useless_string_raw;
pub mod no_useless_undefined;
Expand Down Expand Up @@ -69,6 +70,7 @@ declare_lint_group! {
self :: no_static_element_interactions :: NoStaticElementInteractions ,
self :: no_substr :: NoSubstr ,
self :: no_template_curly_in_string :: NoTemplateCurlyInString ,
self :: no_ts_ignore :: NoTsIgnore ,
self :: no_useless_escape_in_regex :: NoUselessEscapeInRegex ,
self :: no_useless_string_raw :: NoUselessStringRaw ,
self :: no_useless_undefined :: NoUselessUndefined ,
Expand Down
145 changes: 145 additions & 0 deletions crates/biome_js_analyze/src/lint/nursery/no_ts_ignore.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use crate::JsRuleAction;
use biome_analyze::{
context::RuleContext, declare_lint_rule, Ast, FixKind, Rule, RuleDiagnostic, RuleSource,
RuleSourceKind,
};
use biome_console::markup;
use biome_diagnostics::Severity;
use biome_js_syntax::{JsModule, JsSyntaxToken, TextLen};
use biome_rowan::{AstNode, BatchMutationExt, Direction, TextRange, TextSize, TriviaPiece};

declare_lint_rule! {
/// Prevents the use of the TypeScript directive `@ts-ignore`.
///
/// The directive `@ts-ignore` suppresses all compilation errors, even ones that could be considered bugs
/// coming from an upstream library or the compiler itself. If you use `@ts-ignore`, it won't be possible to know
/// when and if the bug is fixed.
///
/// The rule promotes the use the directive `@ts-expect-error`, which is meant to raise an error if there aren't any errors.
/// This means that once the bug is fixed, you can delete the directive, safely.
///
/// ## Examples
///
/// ### Invalid
///
/// ```ts,expect_diagnostic
/// // @ts-ignore
/// let foo;
/// ```
///
/// ### Valid
///
/// ```ts
/// // @ts-expect-error
/// let foo;
/// ```
///
pub NoTsIgnore {
version: "next",
name: "noTsIgnore",
language: "js",
sources: &[RuleSource::Eslint("ban-ts-comment")],
recommended: true,
source_kind: RuleSourceKind::Inspired,
fix_kind: FixKind::Safe,
severity: Severity::Warning,
}
}

/// We track the token that has the trivia, and the range when the incorrect comment is the document
type RuleState = (JsSyntaxToken, TextRange);

impl Rule for NoTsIgnore {
type Query = Ast<JsModule>;
type State = RuleState;
type Signals = Vec<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let module = ctx.query();

let mut tokens = vec![];
for token in module.syntax().descendants_tokens(Direction::Next) {
let leading_trivia = token.leading_trivia();
let comments: Vec<_> = leading_trivia
.pieces()
.filter_map(|trivia| {
if let Some(comment) = trivia.as_comments() {
if let Some((index, _)) = comment.text().match_indices("@ts-ignore").next()
{
return Some((
token.clone(),
comment.text_range().add_start(TextSize::from(index as u32)),
));
}
}
None
})
.collect();

tokens.extend(comments);
}

tokens
}

fn diagnostic(_ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
let (token, range) = state;

Some(
RuleDiagnostic::new(
rule_category!(),
range,
markup! {
"Unsafe use of the "<Emphasis>"@ts-ignore"</Emphasis>" directive found in this comment."
},
)
.detail(
token.text_trimmed_range(),
markup! {
"The directive is applied to this line."
},
)
.note(markup! {
"The "<Emphasis>"@ts-ignore"</Emphasis>" directive suppresses any kind of error, even possible errors that might be fixed by upstream libraries or the compiler itself."
}),
)
}

fn action(ctx: &RuleContext<Self>, state: &Self::State) -> Option<JsRuleAction> {
let (token, _) = state;
let token = token.clone();
let mut mutation = ctx.root().begin();
let mut new_trivia = vec![];
let mut text = String::new();
for trivia in token.clone().leading_trivia().pieces() {
let kind = trivia.kind();
if let Some(comment) = trivia.as_comments() {
if comment.text().contains("@ts-ignore") {
let new_comment = comment.text().replace("@ts-ignore", "@ts-expect-error");
new_trivia.push(TriviaPiece::new(kind, new_comment.text_len()));
text.push_str(new_comment.as_str());
} else {
new_trivia.push(TriviaPiece::new(kind, comment.text_len()));
text.push_str(comment.text());
}
} else {
new_trivia.push(TriviaPiece::new(kind, trivia.text_len()));
text.push_str(trivia.text());
}
}
text.push_str(token.text_trimmed());
let new_token = JsSyntaxToken::new_detached(token.kind(), text.as_str(), new_trivia, [])
.with_trailing_trivia_pieces(token.trailing_trivia().pieces());

mutation.replace_token_discard_trivia(token, new_token);

Some(JsRuleAction::new(
ctx.metadata().action_category(ctx.category(), ctx.group()),
ctx.metadata().applicability(),
markup! { "Use the "<Emphasis>"@ts-expect-error"</Emphasis>" directive instead." }
.to_owned(),
mutation,
))
}
}
1 change: 1 addition & 0 deletions crates/biome_js_analyze/src/options.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions crates/biome_js_analyze/tests/specs/nursery/noTsIgnore/invalid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

// @ts-ignore
let found;

/**
* @ts-ignore
* more comments
*/
let foo;


// Some comment
// @ts-ignore
// Some other comment
let bar;


// Some comment
// @ts-ignore Some other comment
let someTextAfter;
163 changes: 163 additions & 0 deletions crates/biome_js_analyze/tests/specs/nursery/noTsIgnore/invalid.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
expression: invalid.ts
snapshot_kind: text
---
# Input
```ts

// @ts-ignore
let found;

/**
* @ts-ignore
* more comments
*/
let foo;


// Some comment
// @ts-ignore
// Some other comment
let bar;


// Some comment
// @ts-ignore Some other comment
let someTextAfter;

```

# Diagnostics
```
invalid.ts:2:4 lint/nursery/noTsIgnore FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! Unsafe use of the @ts-ignore directive found in this comment.

> 2 │ // @ts-ignore
│ ^^^^^^^^^^
3 │ let found;
4 │

i The directive is applied to this line.

2 │ // @ts-ignore
> 3 │ let found;
│ ^^^
4 │
5 │ /**

i The @ts-ignore directive suppresses any kind of error, even possible errors that might be fixed by upstream libraries or the compiler itself.

i Safe fix: Use the @ts-expect-error directive instead.

1 1 │
2 │ - //·@ts-ignore
2 │ + //·@ts-expect-error
3 3 │ let found;
4 4 │


```

```
invalid.ts:6:4 lint/nursery/noTsIgnore FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! Unsafe use of the @ts-ignore directive found in this comment.

5 │ /**
> 6 │ * @ts-ignore
│ ^^^^^^^^^^
> 7 │ * more comments
> 8 │ */
│ ^^
9 │ let foo;
10 │

i The directive is applied to this line.

7 │ * more comments
8 │ */
> 9 │ let foo;
│ ^^^
10 │

i The @ts-ignore directive suppresses any kind of error, even possible errors that might be fixed by upstream libraries or the compiler itself.

i Safe fix: Use the @ts-expect-error directive instead.

4 4 │
5 5 │ /**
6 │ - ·*·@ts-ignore
6 │ + ·*·@ts-expect-error
7 7 │ * more comments
8 8 │ */


```

```
invalid.ts:13:4 lint/nursery/noTsIgnore FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! Unsafe use of the @ts-ignore directive found in this comment.

12 │ // Some comment
> 13 │ // @ts-ignore
│ ^^^^^^^^^^
14 │ // Some other comment
15 │ let bar;

i The directive is applied to this line.

13 │ // @ts-ignore
14 │ // Some other comment
> 15 │ let bar;
│ ^^^
16 │

i The @ts-ignore directive suppresses any kind of error, even possible errors that might be fixed by upstream libraries or the compiler itself.

i Safe fix: Use the @ts-expect-error directive instead.

11 11 │
12 12 │ // Some comment
13 │ - //·@ts-ignore
13 │ + //·@ts-expect-error
14 14 │ // Some other comment
15 15 │ let bar;


```

```
invalid.ts:19:4 lint/nursery/noTsIgnore FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! Unsafe use of the @ts-ignore directive found in this comment.

18 │ // Some comment
> 19 │ // @ts-ignore Some other comment
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
20 │ let someTextAfter;
21 │

i The directive is applied to this line.

18 │ // Some comment
19 │ // @ts-ignore Some other comment
> 20 │ let someTextAfter;
│ ^^^
21 │

i The @ts-ignore directive suppresses any kind of error, even possible errors that might be fixed by upstream libraries or the compiler itself.

i Safe fix: Use the @ts-expect-error directive instead.

17 17 │
18 18 │ // Some comment
19 │ - //·@ts-ignore·Some·other·comment
19 │ + //·@ts-expect-error·Some·other·comment
20 20 │ let someTextAfter;
21 21 │


```
Loading