Skip to content
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
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ mod typescript {
pub mod no_this_alias;
pub mod no_unnecessary_type_constraint;
pub mod no_unsafe_declaration_merging;
pub mod no_unsafe_function_type;
pub mod no_useless_empty_export;
pub mod no_var_requires;
pub mod no_wrapper_object_types;
Expand Down Expand Up @@ -842,6 +843,7 @@ oxc_macros::declare_all_lint_rules! {
typescript::no_this_alias,
typescript::no_unnecessary_type_constraint,
typescript::no_unsafe_declaration_merging,
typescript::no_unsafe_function_type,
typescript::no_useless_empty_export,
typescript::no_var_requires,
typescript::no_wrapper_object_types,
Expand Down
121 changes: 121 additions & 0 deletions crates/oxc_linter/src/rules/typescript/no_unsafe_function_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use oxc_ast::{
ast::{Expression, IdentifierReference, TSTypeName},
AstKind,
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::IsGlobalReference;
use oxc_span::Span;

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

fn no_unsafe_function_type_diagnostic(span: Span) -> OxcDiagnostic {
// See <https://oxc.rs/docs/contribute/linter/adding-rules.html#diagnostics> for details
OxcDiagnostic::warn("The `Function` type accepts any function-like value.")
.with_help("Prefer explicitly defining any function parameters and return type.")
.with_label(span)
}

#[derive(Debug, Default, Clone)]
pub struct NoUnsafeFunctionType;

declare_oxc_lint!(
/// ### What it does
/// Disallow using the unsafe built-in Function type.
///
/// ### Why is this bad?
/// TypeScript's built-in Function type allows being called with any number of arguments and returns type any. Function also allows classes or plain objects that happen to possess all properties of the Function class. It's generally better to specify function parameters and return types with the function type syntax.
///
/// ### Examples
///
/// Examples of **incorrect** code for this rule:
/// ```ts
/// let noParametersOrReturn: Function;
/// noParametersOrReturn = () => {};
///
/// let stringToNumber: Function;
/// stringToNumber = (text: string) => text.length;
///
/// let identity: Function;
/// identity = value => value;
/// ```
///
/// Examples of **correct** code for this rule:
/// ```ts
/// let noParametersOrReturn: () => void;
/// noParametersOrReturn = () => {};
///
/// let stringToNumber: (text: string) => number;
/// stringToNumber = text => text.length;
///
/// let identity: <T>(value: T) => T;
/// identity = value => value;
/// ```
NoUnsafeFunctionType,
pedantic,
);

impl Rule for NoUnsafeFunctionType {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
match node.kind() {
AstKind::TSTypeReference(reference) => {
if let TSTypeName::IdentifierReference(ident_ref) = &reference.type_name {
handle_function_type(ident_ref, ctx);
}
}
AstKind::TSClassImplements(implements) => {
if let TSTypeName::IdentifierReference(ident_ref) = &implements.expression {
handle_function_type(ident_ref, ctx);
}
}
AstKind::TSInterfaceHeritage(heritage) => {
if let Expression::Identifier(ident) = &heritage.expression {
handle_function_type(ident, ctx);
}
}
_ => {}
}
}
}

fn handle_function_type<'a>(identifier: &'a IdentifierReference<'a>, ctx: &LintContext<'a>) {
if identifier.name == "Function" && identifier.is_global_reference(ctx.symbols()) {
ctx.diagnostic(no_unsafe_function_type_diagnostic(identifier.span));
}
}

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

let pass = vec![
"let value: () => void;",
"let value: <T>(t: T) => T;",
"
// create a scope since it's illegal to declare a duplicate identifier
// 'Function' in the global script scope.
{
type Function = () => void;
let value: Function;
}
",
];

let fail = vec![
"let value: Function;",
"let value: Function[];",
"let value: Function | number;",
"
class Weird implements Function {
// ...
}
",
"
interface Weird extends Function {
// ...
}
",
];

Tester::new(NoUnsafeFunctionType::NAME, pass, fail).test_and_snapshot();
}
41 changes: 41 additions & 0 deletions crates/oxc_linter/src/snapshots/no_unsafe_function_type.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
source: crates/oxc_linter/src/tester.rs
---
⚠ typescript-eslint(no-unsafe-function-type): The `Function` type accepts any function-like value.
╭─[no_unsafe_function_type.tsx:1:12]
1 │ let value: Function;
· ────────
╰────
help: Prefer explicitly defining any function parameters and return type.

⚠ typescript-eslint(no-unsafe-function-type): The `Function` type accepts any function-like value.
╭─[no_unsafe_function_type.tsx:1:12]
1 │ let value: Function[];
· ────────
╰────
help: Prefer explicitly defining any function parameters and return type.

⚠ typescript-eslint(no-unsafe-function-type): The `Function` type accepts any function-like value.
╭─[no_unsafe_function_type.tsx:1:12]
1 │ let value: Function | number;
· ────────
╰────
help: Prefer explicitly defining any function parameters and return type.

⚠ typescript-eslint(no-unsafe-function-type): The `Function` type accepts any function-like value.
╭─[no_unsafe_function_type.tsx:2:35]
1 │
2 │ class Weird implements Function {
· ────────
3 │ // ...
╰────
help: Prefer explicitly defining any function parameters and return type.

⚠ typescript-eslint(no-unsafe-function-type): The `Function` type accepts any function-like value.
╭─[no_unsafe_function_type.tsx:2:36]
1 │
2 │ interface Weird extends Function {
· ────────
3 │ // ...
╰────
help: Prefer explicitly defining any function parameters and return type.