From 2ac9f963df6f2b1f01a85579f44908e96e77410a Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:49:31 +0000 Subject: [PATCH] feat(linter): typescript/no-inferrable-types (#7438) --- .typos.toml | 1 + crates/oxc_linter/src/rules.rs | 2 + .../rules/typescript/no_inferrable_types.rs | 518 ++++++++++++++++++ .../src/snapshots/no_inferrable_types.snap | 351 ++++++++++++ 4 files changed, 872 insertions(+) create mode 100644 crates/oxc_linter/src/rules/typescript/no_inferrable_types.rs create mode 100644 crates/oxc_linter/src/snapshots/no_inferrable_types.snap diff --git a/.typos.toml b/.typos.toml index 370fa7d77b344..8f2883d57c548 100644 --- a/.typos.toml +++ b/.typos.toml @@ -40,6 +40,7 @@ xdescribe = "xdescribe" seeked = "seeked" labeledby = "labeledby" hashi = "hashi" +inferrable = "inferrable" [default.extend-identifiers] IIFEs = "IIFEs" diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 73ad743845195..24276f83686ab 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -168,6 +168,7 @@ mod typescript { pub mod no_extra_non_null_assertion; pub mod no_extraneous_class; pub mod no_import_type_side_effects; + pub mod no_inferrable_types; pub mod no_misused_new; pub mod no_namespace; pub mod no_non_null_asserted_nullish_coalescing; @@ -851,6 +852,7 @@ oxc_macros::declare_all_lint_rules! { typescript::consistent_type_definitions, typescript::consistent_type_imports, typescript::explicit_function_return_type, + typescript::no_inferrable_types, typescript::no_confusing_non_null_assertion, typescript::no_duplicate_enum_values, typescript::no_dynamic_delete, diff --git a/crates/oxc_linter/src/rules/typescript/no_inferrable_types.rs b/crates/oxc_linter/src/rules/typescript/no_inferrable_types.rs new file mode 100644 index 0000000000000..66aee206c18ff --- /dev/null +++ b/crates/oxc_linter/src/rules/typescript/no_inferrable_types.rs @@ -0,0 +1,518 @@ +use crate::{context::LintContext, rule::Rule, AstNode}; +use oxc_ast::{ + ast::{ + BindingPatternKind, ChainElement, Expression, FormalParameter, TSLiteral, TSType, + TSTypeAnnotation, TSTypeName, UnaryOperator, + }, + AstKind, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::GetSpan; +use oxc_span::Span; + +fn no_inferrable_types_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Type can be trivially inferred from the initializer") + .with_help("Remove the type annotation") + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct NoInferrableTypes { + ignore_parameters: bool, + ignore_properties: bool, +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow explicit type declarations for variables or parameters initialized to a number, string, or boolean + /// + /// ### Why is this bad? + /// + /// Explicitly typing variables or parameters that are initialized to a literal value is unnecessary because TypeScript can infer the type from the value. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```ts + /// const a: number = 5; + /// const b: string = 'foo'; + /// const c: boolean = true; + /// const fn = (a: number = 5, b: boolean = true, c: string = 'foo') => {}; + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```ts + /// const a = 5; + /// const b = 'foo'; + /// const c = true; + /// const fn = (a = 5, b = true, c = 'foo') => {}; + /// ``` + NoInferrableTypes, + style, + pending +); + +impl Rule for NoInferrableTypes { + fn from_configuration(value: serde_json::Value) -> Self { + use serde_json::Value; + let Some(config) = value.get(0).and_then(Value::as_object) else { + return Self::default(); + }; + Self { + ignore_parameters: config + .get("ignoreParameters") + .and_then(Value::as_bool) + .unwrap_or(false), + ignore_properties: config + .get("ignoreProperties") + .and_then(Value::as_bool) + .unwrap_or(false), + } + } + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::VariableDeclarator(variable_decl) => { + if let (Some(init), Some(type_annotation)) = + (&variable_decl.init, &variable_decl.id.type_annotation) + { + if is_inferrable_type(type_annotation, init) { + ctx.diagnostic(no_inferrable_types_diagnostic(type_annotation.span())); + } + } + } + AstKind::Function(function) => { + self.check_formal_parameters(&function.params.items, ctx); + } + AstKind::ArrowFunctionExpression(arrow_function_expression) => { + self.check_formal_parameters(&arrow_function_expression.params.items, ctx); + } + AstKind::PropertyDefinition(property_definition) => { + // We ignore `readonly` because of Microsoft/TypeScript#14416 + // Essentially a readonly property without a type + // will result in its value being the type, leading to + // compile errors if the type is stripped. + if self.ignore_properties + || property_definition.readonly + || property_definition.optional + { + return; + } + if let (Some(init), Some(type_annotation)) = + (&property_definition.value, &property_definition.type_annotation) + { + if is_inferrable_type(type_annotation, init) { + ctx.diagnostic(no_inferrable_types_diagnostic(type_annotation.span())); + } + } + } + _ => {} + } + } +} + +impl NoInferrableTypes { + fn check_formal_parameters<'a>( + &self, + params: &oxc_allocator::Vec<'a, FormalParameter<'a>>, + ctx: &LintContext<'a>, + ) { + if self.ignore_parameters { + return; + } + + for param in params { + if let BindingPatternKind::AssignmentPattern(param_assignment_pat) = ¶m.pattern.kind + { + if let Some(type_annotation) = ¶m_assignment_pat.left.type_annotation { + if is_inferrable_type(type_annotation, ¶m_assignment_pat.right) { + ctx.diagnostic(no_inferrable_types_diagnostic(type_annotation.span())); + } + } + } + } + } +} + +fn is_inferrable_type(type_annotation: &TSTypeAnnotation, init: &Expression) -> bool { + match &type_annotation.type_annotation { + TSType::TSLiteralType(ts_literal_type) => match &ts_literal_type.literal { + TSLiteral::BooleanLiteral(_) => is_init_boolean(init), + TSLiteral::NullLiteral(_) => is_init_null(init), + TSLiteral::NumericLiteral(_) => is_init_number(init), + TSLiteral::BigIntLiteral(_) => is_init_bigint(init), + TSLiteral::StringLiteral(_) => is_init_string(init), + TSLiteral::RegExpLiteral(_) => is_init_regexp(init), + TSLiteral::TemplateLiteral(_) | TSLiteral::UnaryExpression(_) => false, + }, + TSType::TSStringKeyword(_) => is_init_string(init), + TSType::TSBigIntKeyword(_) => is_init_bigint(init), + TSType::TSBooleanKeyword(_) => is_init_boolean(init), + TSType::TSNumberKeyword(_) => is_init_number(init), + TSType::TSNullKeyword(_) => is_init_null(init), + TSType::TSSymbolKeyword(_) => { + if is_chain_call_expression_with_name(init, "Symbol") { + return true; + } + if let Expression::CallExpression(call_expr) = init.get_inner_expression() { + call_expr.callee.get_identifier_reference().map_or(false, |id| id.name == "Symbol") + } else { + false + } + } + TSType::TSTypeReference(type_reference) => { + if let TSTypeName::IdentifierReference(ident) = &type_reference.type_name { + if ident.name == "RegExp" { + return is_init_regexp(init); + } + } + + false + } + TSType::TSUndefinedKeyword(_) => match init.get_inner_expression() { + Expression::Identifier(id) => id.name == "undefined", + Expression::UnaryExpression(unary_expr) => { + matches!(unary_expr.operator, UnaryOperator::Void) + } + _ => false, + }, + _ => false, + } +} + +fn is_chain_call_expression_with_name(init: &Expression, name: &str) -> bool { + if let Expression::ChainExpression(chain_expr) = init { + if let ChainElement::CallExpression(call_expr) = &chain_expr.expression { + return call_expr.callee.get_identifier_reference().map_or(false, |id| id.name == name); + } + } + false +} + +fn is_init_bigint(init: &Expression) -> bool { + let init = { + let init = init.get_inner_expression(); + if let Expression::UnaryExpression(unary_expr) = init { + if matches!( + unary_expr.operator, + UnaryOperator::UnaryPlus | UnaryOperator::UnaryNegation + ) { + unary_expr.argument.get_inner_expression() + } else { + init + } + } else { + init + } + }; + + if is_chain_call_expression_with_name(init, "BigInt") { + return true; + } + + match init { + Expression::CallExpression(call_expr) => { + call_expr.callee.get_identifier_reference().map_or(false, |id| id.name == "BigInt") + } + Expression::BigIntLiteral(_) => true, + _ => false, + } +} + +fn is_init_boolean(init: &Expression) -> bool { + if is_chain_call_expression_with_name(init, "Boolean") { + return true; + } + match init.get_inner_expression() { + Expression::UnaryExpression(unary_expr) => { + matches!(unary_expr.operator, UnaryOperator::LogicalNot) + } + Expression::CallExpression(call_expr) => { + call_expr.callee.get_identifier_reference().map_or(false, |id| id.name == "Boolean") + } + Expression::BooleanLiteral(_) => true, + _ => false, + } +} + +fn is_init_null(init: &Expression) -> bool { + let init = init.get_inner_expression(); + matches!(init, Expression::NullLiteral(_)) +} + +fn is_init_number(init: &Expression) -> bool { + let init = { + let init = init.get_inner_expression(); + if let Expression::UnaryExpression(unary_expr) = init { + if matches!( + unary_expr.operator, + UnaryOperator::UnaryPlus | UnaryOperator::UnaryNegation + ) { + unary_expr.argument.get_inner_expression() + } else { + init + } + } else { + init + } + }; + if is_chain_call_expression_with_name(init, "Number") { + return true; + } + match init { + Expression::Identifier(id) => id.name == "Infinity" || id.name == "NaN", + Expression::CallExpression(call_expr) => { + call_expr.callee.get_identifier_reference().map_or(false, |id| id.name == "Number") + } + Expression::NumericLiteral(_) => true, + _ => false, + } +} +fn is_init_string(init: &Expression) -> bool { + if is_chain_call_expression_with_name(init, "String") { + return true; + } + match init { + Expression::CallExpression(call_expr) => { + call_expr.callee.get_identifier_reference().map_or(false, |id| id.name == "String") + } + Expression::StringLiteral(_) | Expression::TemplateLiteral(_) => true, + _ => false, + } +} +fn is_init_regexp(init: &Expression) -> bool { + if is_chain_call_expression_with_name(init, "RegExp") { + return true; + } + match init.get_inner_expression() { + Expression::RegExpLiteral(_) => true, + Expression::NewExpression(new_expr) => { + if let Expression::Identifier(id) = new_expr.callee.get_inner_expression() { + id.name == "RegExp" + } else { + false + } + } + Expression::CallExpression(call_expr) => { + if let Expression::Identifier(id) = call_expr.callee.get_inner_expression() { + id.name == "RegExp" + } else { + false + } + } + _ => false, + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + (r"const a = 10n", None), + (r"const a = -10n", None), + (r"const a = BigInt(10)", None), + (r"const a = -BigInt(10)", None), + (r"const a = BigInt?.(10)", None), + (r"const a = -BigInt?.(10)", None), + (r"const a = false", None), + (r"const a = true", None), + (r"const a = Boolean(null)", None), + (r"const a = Boolean?.(null)", None), + (r"const a = !0", None), + (r"const a = 10", None), + (r"const a = +10", None), + (r"const a = -10", None), + (r#"const a = Number("1")"#, None), + (r#"const a = +Number("1")"#, None), + (r#"const a = -Number("1")"#, None), + (r#"const a = Number?.("1")"#, None), + (r#"const a = +Number?.("1")"#, None), + (r#"const a = -Number?.("1")"#, None), + (r"const a = Infinity", None), + (r"const a = +Infinity", None), + (r"const a = -Infinity", None), + (r"const a = NaN", None), + (r"const a = +NaN", None), + (r"const a = -NaN", None), + (r"const a = null", None), + (r"const a = /a/", None), + (r#"const a = RegExp("a")"#, None), + (r#"const a = RegExp?.("a")"#, None), + (r#"const a = new RegExp("a")"#, None), + (r#"const a = "str""#, None), + (r"const a = 'str'", None), + (r"const a = `str`", None), + (r"const a = String(1)", None), + (r"const a = String?.(1)", None), + (r#"const a = Symbol("a")"#, None), + (r#"const a = Symbol?.("a")"#, None), + (r"const a = undefined", None), + (r"const a = void someValue", None), + ("const fn = (a = 5, b = true, c = 'foo') => {};", None), + ("const fn = function (a = 5, b = true, c = 'foo') {};", None), + ("function fn(a = 5, b = true, c = 'foo') {}", None), + ("function fn(a: number, b: boolean, c: string) {}", None), + ( + " + class Foo { + a = 5; + b = true; + c = 'foo'; + }", + None, + ), + ("class Foo { readonly a: number = 5; }", None), + ("const a: any = 5;", None), + ("const fn = function (a: any = 5, b: any = true, c: any = 'foo') {};", None), + ( + "const fn = (a: number = 5, b: boolean = true, c: string = 'foo') => {};", + Some(serde_json::json!([{ "ignoreParameters": true }])), + ), + ( + "function fn(a: number = 5, b: boolean = true, c: string = 'foo') {}", + Some(serde_json::json!([{ "ignoreParameters": true }])), + ), + ( + "const fn = function (a: number = 5, b: boolean = true, c: string = 'foo') {};", + Some(serde_json::json!([{ "ignoreParameters": true }])), + ), + ( + " + class Foo { + a: number = 5; + b: boolean = true; + c: string = 'foo'; + }", + Some(serde_json::json!([{ "ignoreProperties": true }])), + ), + ( + " + class Foo { + a?: number = 5; + b?: boolean = true; + c?: string = 'foo'; + }", + None, + ), + ("class Foo { constructor(public a = true) {} }", None), + ]; + + let fail = vec![ + (r"const a: bigint = 10n", None), + (r"const a: bigint = -10n", None), + (r"const a: bigint = BigInt(10)", None), + (r"const a: bigint = -BigInt(10)", None), + (r"const a: bigint = BigInt?.(10)", None), + (r"const a: bigint = -BigInt?.(10)", None), + (r"const a: boolean = false", None), + (r"const a: boolean = true", None), + (r"const a: boolean = Boolean(null)", None), + (r"const a: boolean = Boolean?.(null)", None), + (r"const a: boolean = !0", None), + (r"const a: number = 10", None), + (r"const a: number = +10", None), + (r"const a: number = -10", None), + (r#"const a: number = Number("1")"#, None), + (r#"const a: number = +Number("1")"#, None), + (r#"const a: number = -Number("1")"#, None), + (r#"const a: number = Number?.("1")"#, None), + (r#"const a: number = +Number?.("1")"#, None), + (r#"const a: number = -Number?.("1")"#, None), + (r"const a: number = Infinity", None), + (r"const a: number = +Infinity", None), + (r"const a: number = -Infinity", None), + (r"const a: number = NaN", None), + (r"const a: number = +NaN", None), + (r"const a: number = -NaN", None), + (r"const a: null = null", None), + (r"const a: RegExp = /a/", None), + (r#"const a: RegExp = RegExp("a")"#, None), + (r#"const a: RegExp = RegExp?.("a")"#, None), + (r#"const a: RegExp = new RegExp("a")"#, None), + (r#"const a: string = "str""#, None), + (r"const a: string = 'str'", None), + (r"const a: string = `str`", None), + (r"const a: string = String(1)", None), + (r"const a: string = String?.(1)", None), + (r#"const a: symbol = Symbol("a")"#, None), + (r#"const a: symbol = Symbol?.("a")"#, None), + (r"const a: undefined = undefined", None), + (r"const a: undefined = void someValue", None), + ( + "const fn = (a?: number = 5) => {};", + Some(serde_json::json!([{ "ignoreParameters": false }])), + ), + ("class A { a!: number = 1; }", Some(serde_json::json!([{ "ignoreProperties": false }]))), + ( + "const fn = (a: number = 5, b: boolean = true, c: string = 'foo') => {};", + Some(serde_json::json!([{ "ignoreParameters": false, "ignoreProperties": false }])), + ), + ( + "class Foo { + a: number = 5; + b: boolean = true; + c: string = 'foo'; + }", + Some(serde_json::json!([{ "ignoreParameters": false, "ignoreProperties": false }])), + ), + ( + "class Foo { constructor(public a: boolean = true) {} }", + Some(serde_json::json!([{ "ignoreParameters": false, "ignoreProperties": false }])), + ), + ]; + + let _fix = vec![ + ( + "const fn = (a?: number = 5) => {};", + "const fn = (a = 5) => {};", + Some(serde_json::json!([{ "ignoreParameters": false, }, ])), + ), + ( + " + class A { + a!: number = 1; + } + ", + " + class A { + a = 1; + } + ", + Some(serde_json::json!([{ "ignoreProperties": false, }, ])), + ), + ( + "const fn = (a: number = 5, b: boolean = true, c: string = 'foo') => {};", + "const fn = (a = 5, b = true, c = 'foo') => {};", + Some( + serde_json::json!([{ "ignoreParameters": false, "ignoreProperties": false, }, ]), + ), + ), + ( + " + class Foo { + a: number = 5; + b: boolean = true; + c: string = 'foo'; + }", + "class Foo { + a = 5; + b = true; + c = 'foo'; + }", + Some( + serde_json::json!([{ "ignoreParameters": false, "ignoreProperties": false, }, ]), + ), + ), + ( + "class Foo { constructor(public a: boolean = true) {} }", + "class Foo { constructor(public a = true) {} } ", + Some(serde_json::json!([{ "ignoreParameters": false, "ignoreProperties": false, }, ])), + ), + ]; + Tester::new(NoInferrableTypes::NAME, pass, fail) + //.expect_fix(fix) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_inferrable_types.snap b/crates/oxc_linter/src/snapshots/no_inferrable_types.snap new file mode 100644 index 0000000000000..877675037a1be --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_inferrable_types.snap @@ -0,0 +1,351 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: bigint = 10n + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: bigint = -10n + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: bigint = BigInt(10) + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: bigint = -BigInt(10) + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: bigint = BigInt?.(10) + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: bigint = -BigInt?.(10) + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: boolean = false + · ───────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: boolean = true + · ───────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: boolean = Boolean(null) + · ───────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: boolean = Boolean?.(null) + · ───────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: boolean = !0 + · ───────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = 10 + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = +10 + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = -10 + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = Number("1") + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = +Number("1") + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = -Number("1") + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = Number?.("1") + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = +Number?.("1") + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = -Number?.("1") + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = Infinity + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = +Infinity + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = -Infinity + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = NaN + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = +NaN + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: number = -NaN + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: null = null + · ────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: RegExp = /a/ + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: RegExp = RegExp("a") + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: RegExp = RegExp?.("a") + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: RegExp = new RegExp("a") + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: string = "str" + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: string = 'str' + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: string = `str` + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: string = String(1) + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: string = String?.(1) + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: symbol = Symbol("a") + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: symbol = Symbol?.("a") + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: undefined = undefined + · ─────────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:8] + 1 │ const a: undefined = void someValue + · ─────────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:15] + 1 │ const fn = (a?: number = 5) => {}; + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:13] + 1 │ class A { a!: number = 1; } + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:14] + 1 │ const fn = (a: number = 5, b: boolean = true, c: string = 'foo') => {}; + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:29] + 1 │ const fn = (a: number = 5, b: boolean = true, c: string = 'foo') => {}; + · ───────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:48] + 1 │ const fn = (a: number = 5, b: boolean = true, c: string = 'foo') => {}; + · ──────── + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:2:16] + 1 │ class Foo { + 2 │ a: number = 5; + · ──────── + 3 │ b: boolean = true; + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:3:16] + 2 │ a: number = 5; + 3 │ b: boolean = true; + · ───────── + 4 │ c: string = 'foo'; + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:4:16] + 3 │ b: boolean = true; + 4 │ c: string = 'foo'; + · ──────── + 5 │ } + ╰──── + help: Remove the type annotation + + ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer + ╭─[no_inferrable_types.tsx:1:33] + 1 │ class Foo { constructor(public a: boolean = true) {} } + · ───────── + ╰──── + help: Remove the type annotation