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: add no-legacy-type-assertion rule #1319

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
17 changes: 17 additions & 0 deletions docs/rules/no_legacy_type_assertion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Disallows the use of legacy `<Type> value` type assertion syntax in TypeScript
code.

`<Type> value` casting syntax is considered to be outdated because it does not
work in JSX. Instead, you should use `value as Type`.

### Invalid:

```typescript
const foo = <Foo> bar;
```

### Valid:

```typescript
const foo = bar as Foo;
```
2 changes: 2 additions & 0 deletions src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub mod no_inner_declarations;
pub mod no_invalid_regexp;
pub mod no_invalid_triple_slash_reference;
pub mod no_irregular_whitespace;
pub mod no_legacy_type_assertion;
pub mod no_misused_new;
pub mod no_namespace;
pub mod no_new_symbol;
Expand Down Expand Up @@ -299,6 +300,7 @@ fn get_all_rules_raw() -> Vec<Box<dyn LintRule>> {
Box::new(no_invalid_regexp::NoInvalidRegexp),
Box::new(no_invalid_triple_slash_reference::NoInvalidTripleSlashReference),
Box::new(no_irregular_whitespace::NoIrregularWhitespace),
Box::new(no_legacy_type_assertion::NoLegacyTypeAssertion),
Box::new(no_misused_new::NoMisusedNew),
Box::new(no_namespace::NoNamespace),
Box::new(no_new_symbol::NoNewSymbol),
Expand Down
118 changes: 118 additions & 0 deletions src/rules/no_legacy_type_assertion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use std::borrow::Cow;

use super::{Context, LintRule};
use crate::diagnostic::{LintFix, LintFixChange};
use crate::handler::{Handler, Traverse};
use crate::Program;
use deno_ast::{view as ast_view, MediaType, SourceRanged};

#[derive(Debug)]
pub struct NoLegacyTypeAssertion;

const CODE: &str = "no-legacy-type-assertion";
const MESSAGE: &str =
"TypeScript's `<Type> value` type assertion syntax is discouraged to use";
const HINT: &str = "Use `as` assertion syntax instead";
const FIX_DESC: &str = "Convert to `as` assertion";

impl LintRule for NoLegacyTypeAssertion {
fn tags(&self) -> &'static [&'static str] {
&["recommended"]
}

fn code(&self) -> &'static str {
CODE
}

fn lint_program_with_ast_view(
&self,
context: &mut Context,
program: Program<'_>,
) {
// The syntax can't appear in js, or jsx/tsx, or d.ts files.
if matches!(
context.media_type(),
MediaType::TypeScript | MediaType::Mts | MediaType::Cts
) {
NoLegacyTypeAssertionHandler.traverse(program, context);
}
}

#[cfg(feature = "docs")]
fn docs(&self) -> &'static str {
include_str!("../../docs/rules/no_legacy_type_assertion.md")
}
}

struct NoLegacyTypeAssertionHandler;

impl Handler for NoLegacyTypeAssertionHandler {
fn ts_type_assertion(
&mut self,
n: &ast_view::TsTypeAssertion,
ctx: &mut Context,
) {
let expr = n.expr.text_fast(ctx.parsed_source());
let typ = n.type_ann.text_fast(ctx.parsed_source());
let fixed = format!("({} as {})", expr, typ);
ctx.add_diagnostic_with_fixes(
n.range(),
CODE,
MESSAGE,
Some(HINT.to_owned()),
vec![LintFix {
changes: vec![LintFixChange {
new_text: Cow::Owned(fixed),
range: n.range(),
}],
description: Cow::Borrowed(FIX_DESC),
}],
);
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn no_legacy_type_assertion_valid() {
assert_lint_ok! {
NoLegacyTypeAssertion,
filename: "file:///foo.ts",

r#"const a = 1 as number"#,
};
}

#[test]
fn no_legacy_type_assertion_invalid() {
assert_lint_err! {
NoLegacyTypeAssertion,
"<number> 1": [
{
col: 0,
message: MESSAGE,
hint: HINT,
fix: (
"Convert to `as` assertion",
"(1 as number)"
)
},
],
"const x = 5 + <number> 5;": [
{
col: 14,
message: MESSAGE,
hint: HINT,
fix: (
"Convert to `as` assertion",
"const x = 5 + (5 as number);"
)
}
],
};
}
}
7 changes: 7 additions & 0 deletions www/static/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,13 @@
"recommended"
]
},
{
"code": "no-legacy-type-assertion",
"docs": "Disallows the use of legacy `<Type> value` type assertion syntax in TypeScript\ncode.\n\n`<Type> value` casting syntax is considered to be outdated because it does not\nwork in JSX. Instead, you should use `value as Type`.\n\n### Invalid:\n\n```typescript\nconst foo = <Foo> bar;\n```\n\n### Valid:\n\n```typescript\nconst foo = bar as Foo;\n```\n",
"tags": [
"recommended"
]
},
{
"code": "no-misused-new",
"docs": "Disallows defining `constructor`s for interfaces or `new` for classes\n\nSpecifying a `constructor` for an interface or defining a `new` method for a\nclass is incorrect and should be avoided.\n\n### Invalid:\n\n```typescript\nclass C {\n new(): C;\n}\n\ninterface I {\n constructor(): void;\n}\n```\n\n### Valid:\n\n```typescript\nclass C {\n constructor() {}\n}\n\ninterface I {\n new (): C;\n}\n```\n",
Expand Down
Loading