diff --git a/crates/oxc_linter/src/config/config_store.rs b/crates/oxc_linter/src/config/config_store.rs index 1700445fee1d7..d174ac237eca6 100644 --- a/crates/oxc_linter/src/config/config_store.rs +++ b/crates/oxc_linter/src/config/config_store.rs @@ -1016,7 +1016,7 @@ mod test { let base_rules = vec![ (RuleEnum::EslintCurly(EslintCurly::default()), AllowWarnDeny::Deny), ( - RuleEnum::TypescriptNoMisusedPromises(TypescriptNoMisusedPromises), + RuleEnum::TypescriptNoMisusedPromises(TypescriptNoMisusedPromises::default()), AllowWarnDeny::Deny, ), ]; diff --git a/crates/oxc_linter/src/rules/typescript/no_confusing_void_expression.rs b/crates/oxc_linter/src/rules/typescript/no_confusing_void_expression.rs index c5e7ffe2a4ad5..c36a076e102b6 100644 --- a/crates/oxc_linter/src/rules/typescript/no_confusing_void_expression.rs +++ b/crates/oxc_linter/src/rules/typescript/no_confusing_void_expression.rs @@ -1,9 +1,25 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::rule::{DefaultRuleConfig, Rule}; #[derive(Debug, Default, Clone)] -pub struct NoConfusingVoidExpression; +pub struct NoConfusingVoidExpression(Box); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)] +#[serde(rename_all = "camelCase", default)] +pub struct NoConfusingVoidExpressionConfig { + /// Whether to ignore arrow function shorthand that returns void. + /// When true, allows expressions like `() => someVoidFunction()`. + pub ignore_arrow_shorthand: bool, + /// Whether to ignore expressions using the void operator. + /// When true, allows `void someExpression`. + pub ignore_void_operator: bool, + /// Whether to ignore calling functions that are declared to return void. + /// When true, allows expressions like `x = voidReturningFunction()`. + pub ignore_void_returning_functions: bool, +} declare_oxc_lint!( /// ### What it does @@ -53,6 +69,19 @@ declare_oxc_lint!( typescript, pedantic, pending, + config = NoConfusingVoidExpressionConfig, ); -impl Rule for NoConfusingVoidExpression {} +impl Rule for NoConfusingVoidExpression { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/rules/typescript/no_duplicate_type_constituents.rs b/crates/oxc_linter/src/rules/typescript/no_duplicate_type_constituents.rs index 577fc1fdb535b..538ef01333278 100644 --- a/crates/oxc_linter/src/rules/typescript/no_duplicate_type_constituents.rs +++ b/crates/oxc_linter/src/rules/typescript/no_duplicate_type_constituents.rs @@ -1,9 +1,22 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::rule::{DefaultRuleConfig, Rule}; #[derive(Debug, Default, Clone)] -pub struct NoDuplicateTypeConstituents; +pub struct NoDuplicateTypeConstituents(Box); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)] +#[serde(rename_all = "camelCase", default)] +pub struct NoDuplicateTypeConstituentsConfig { + /// Whether to ignore duplicate types in intersection types. + /// When true, allows `type T = A & A`. + pub ignore_intersections: bool, + /// Whether to ignore duplicate types in union types. + /// When true, allows `type T = A | A`. + pub ignore_unions: bool, +} declare_oxc_lint!( /// ### What it does @@ -51,6 +64,19 @@ declare_oxc_lint!( typescript, correctness, pending, + config = NoDuplicateTypeConstituentsConfig, ); -impl Rule for NoDuplicateTypeConstituents {} +impl Rule for NoDuplicateTypeConstituents { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/rules/typescript/no_floating_promises.rs b/crates/oxc_linter/src/rules/typescript/no_floating_promises.rs index c14c3d3e81318..a6af71d6b5b4a 100644 --- a/crates/oxc_linter/src/rules/typescript/no_floating_promises.rs +++ b/crates/oxc_linter/src/rules/typescript/no_floating_promises.rs @@ -2,7 +2,10 @@ use oxc_macros::declare_oxc_lint; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::rule::{DefaultRuleConfig, Rule}; +use crate::{ + rule::{DefaultRuleConfig, Rule}, + utils::TypeOrValueSpecifier, +}; #[derive(Debug, Default, Clone)] pub struct NoFloatingPromises(Box); @@ -23,111 +26,6 @@ pub struct NoFloatingPromisesConfig { pub ignore_void: bool, } -/// Type or value specifier for matching specific declarations -/// -/// Supports four types of specifiers: -/// -/// 1. **String specifier** (deprecated): Universal match by name -/// ```json -/// "Promise" -/// ``` -/// -/// 2. **File specifier**: Match types/values declared in local files -/// ```json -/// { "from": "file", "name": "MyType" } -/// { "from": "file", "name": ["Type1", "Type2"] } -/// { "from": "file", "name": "MyType", "path": "./types.ts" } -/// ``` -/// -/// 3. **Lib specifier**: Match TypeScript built-in lib types -/// ```json -/// { "from": "lib", "name": "Promise" } -/// { "from": "lib", "name": ["Promise", "PromiseLike"] } -/// ``` -/// -/// 4. **Package specifier**: Match types/values from npm packages -/// ```json -/// { "from": "package", "name": "Observable", "package": "rxjs" } -/// { "from": "package", "name": ["Observable", "Subject"], "package": "rxjs" } -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(untagged)] -pub enum TypeOrValueSpecifier { - /// Universal string specifier - matches all types and values with this name regardless of declaration source. - /// Not recommended - will be removed in a future major version. - String(String), - /// Describes specific types or values declared in local files. - File(FileSpecifier), - /// Describes specific types or values declared in TypeScript's built-in lib.*.d.ts types. - Lib(LibSpecifier), - /// Describes specific types or values imported from packages. - Package(PackageSpecifier), -} - -/// Describes specific types or values declared in local files. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "camelCase")] -pub struct FileSpecifier { - /// Must be "file" - from: FileFrom, - /// The name(s) of the type or value to match - name: NameSpecifier, - /// Optional file path to specify where the types or values must be declared. - /// If omitted, all files will be matched. - #[serde(skip_serializing_if = "Option::is_none")] - path: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "lowercase")] -enum FileFrom { - File, -} - -/// Describes specific types or values declared in TypeScript's built-in lib.*.d.ts types. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "camelCase")] -pub struct LibSpecifier { - /// Must be "lib" - from: LibFrom, - /// The name(s) of the lib type or value to match - name: NameSpecifier, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "lowercase")] -enum LibFrom { - Lib, -} - -/// Describes specific types or values imported from packages. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "camelCase")] -pub struct PackageSpecifier { - /// Must be "package" - from: PackageFrom, - /// The name(s) of the type or value to match - name: NameSpecifier, - /// The package name to match - package: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "lowercase")] -enum PackageFrom { - Package, -} - -/// Name specifier that can be a single string or array of strings -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(untagged)] -pub enum NameSpecifier { - /// Single name - Single(String), - /// Multiple names - Multiple(Vec), -} - impl Default for NoFloatingPromisesConfig { fn default() -> Self { Self { diff --git a/crates/oxc_linter/src/rules/typescript/no_misused_promises.rs b/crates/oxc_linter/src/rules/typescript/no_misused_promises.rs index 3436a727e7c93..5aeb5dc9566f6 100644 --- a/crates/oxc_linter/src/rules/typescript/no_misused_promises.rs +++ b/crates/oxc_linter/src/rules/typescript/no_misused_promises.rs @@ -1,9 +1,89 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::{ + rule::{DefaultRuleConfig, Rule}, + utils::default_true, +}; + +fn default_checks_void_return() -> ChecksVoidReturn { + ChecksVoidReturn::Boolean(true) +} #[derive(Debug, Default, Clone)] -pub struct NoMisusedPromises; +pub struct NoMisusedPromises(Box); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase", default)] +#[expect(clippy::struct_field_names)] +pub struct NoMisusedPromisesConfig { + /// Whether to check if Promises are used in conditionals. + /// When true, disallows using Promises in conditions where a boolean is expected. + #[serde(default = "default_true")] + pub checks_conditionals: bool, + /// Whether to check if Promises are used in spread syntax. + /// When true, disallows spreading Promise values. + #[serde(default = "default_true")] + pub checks_spreads: bool, + /// Configuration for checking if Promises are returned in contexts expecting void. + /// Can be a boolean to enable/disable all checks, or an object for granular control. + #[serde(default = "default_checks_void_return")] + pub checks_void_return: ChecksVoidReturn, +} + +impl Default for NoMisusedPromisesConfig { + fn default() -> Self { + Self { + checks_conditionals: true, + checks_spreads: true, + checks_void_return: ChecksVoidReturn::Boolean(true), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum ChecksVoidReturn { + Boolean(bool), + Options(ChecksVoidReturnOptions), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase", default)] +pub struct ChecksVoidReturnOptions { + /// Whether to check Promise-returning functions passed as arguments to void-returning functions. + #[serde(default = "default_true")] + pub arguments: bool, + /// Whether to check Promise-returning functions in JSX attributes expecting void. + #[serde(default = "default_true")] + pub attributes: bool, + /// Whether to check Promise-returning methods that override void-returning inherited methods. + #[serde(default = "default_true")] + pub inherited_methods: bool, + /// Whether to check Promise-returning functions assigned to object properties expecting void. + #[serde(default = "default_true")] + pub properties: bool, + /// Whether to check Promise values returned from void-returning functions. + #[serde(default = "default_true")] + pub returns: bool, + /// Whether to check Promise-returning functions assigned to variables typed as void-returning. + #[serde(default = "default_true")] + pub variables: bool, +} + +impl Default for ChecksVoidReturnOptions { + fn default() -> Self { + Self { + arguments: true, + attributes: true, + inherited_methods: true, + properties: true, + returns: true, + variables: true, + } + } +} declare_oxc_lint!( /// ### What it does @@ -55,6 +135,19 @@ declare_oxc_lint!( typescript, pedantic, pending, + config = NoMisusedPromisesConfig, ); -impl Rule for NoMisusedPromises {} +impl Rule for NoMisusedPromises { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/rules/typescript/no_unnecessary_boolean_literal_compare.rs b/crates/oxc_linter/src/rules/typescript/no_unnecessary_boolean_literal_compare.rs index 8313f12344750..340087c666d39 100644 --- a/crates/oxc_linter/src/rules/typescript/no_unnecessary_boolean_literal_compare.rs +++ b/crates/oxc_linter/src/rules/typescript/no_unnecessary_boolean_literal_compare.rs @@ -1,9 +1,40 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::{ + rule::{DefaultRuleConfig, Rule}, + utils::default_true, +}; #[derive(Debug, Default, Clone)] -pub struct NoUnnecessaryBooleanLiteralCompare; +pub struct NoUnnecessaryBooleanLiteralCompare(Box); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase", default)] +pub struct NoUnnecessaryBooleanLiteralCompareConfig { + /// Whether to allow comparing nullable boolean expressions to `false`. + /// When false, `x === false` where x is `boolean | null` will be flagged. + #[serde(default = "default_true")] + pub allow_comparing_nullable_booleans_to_false: bool, + /// Whether to allow comparing nullable boolean expressions to `true`. + /// When false, `x === true` where x is `boolean | null` will be flagged. + #[serde(default = "default_true")] + pub allow_comparing_nullable_booleans_to_true: bool, + /// Whether to allow this rule to run without `strictNullChecks` enabled. + /// This is not recommended as the rule may produce incorrect results. + pub allow_rule_to_run_without_strict_null_checks_i_know_what_i_am_doing: bool, +} + +impl Default for NoUnnecessaryBooleanLiteralCompareConfig { + fn default() -> Self { + Self { + allow_comparing_nullable_booleans_to_false: true, + allow_comparing_nullable_booleans_to_true: true, + allow_rule_to_run_without_strict_null_checks_i_know_what_i_am_doing: false, + } + } +} declare_oxc_lint!( /// ### What it does @@ -61,6 +92,21 @@ declare_oxc_lint!( typescript, suspicious, pending, + config = NoUnnecessaryBooleanLiteralCompareConfig, ); -impl Rule for NoUnnecessaryBooleanLiteralCompare {} +impl Rule for NoUnnecessaryBooleanLiteralCompare { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>( + value, + ) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/rules/typescript/no_unnecessary_type_assertion.rs b/crates/oxc_linter/src/rules/typescript/no_unnecessary_type_assertion.rs index 606030a77a45b..636c3a0874ba4 100644 --- a/crates/oxc_linter/src/rules/typescript/no_unnecessary_type_assertion.rs +++ b/crates/oxc_linter/src/rules/typescript/no_unnecessary_type_assertion.rs @@ -1,9 +1,20 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::rule::{DefaultRuleConfig, Rule}; #[derive(Debug, Default, Clone)] -pub struct NoUnnecessaryTypeAssertion; +pub struct NoUnnecessaryTypeAssertion(Box); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)] +#[serde(rename_all = "camelCase", default)] +pub struct NoUnnecessaryTypeAssertionConfig { + /// A list of type names to ignore when checking for unnecessary assertions. + /// Type assertions to these types will not be flagged even if they appear unnecessary. + /// Example: `["Foo", "Bar"]` to allow `x as Foo` or `x as Bar`. + pub types_to_ignore: Vec, +} declare_oxc_lint!( /// ### What it does @@ -51,6 +62,19 @@ declare_oxc_lint!( typescript, suspicious, pending, + config = NoUnnecessaryTypeAssertionConfig, ); -impl Rule for NoUnnecessaryTypeAssertion {} +impl Rule for NoUnnecessaryTypeAssertion { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/rules/typescript/only_throw_error.rs b/crates/oxc_linter/src/rules/typescript/only_throw_error.rs index a3a9c5e13507e..576009e3037fe 100644 --- a/crates/oxc_linter/src/rules/typescript/only_throw_error.rs +++ b/crates/oxc_linter/src/rules/typescript/only_throw_error.rs @@ -1,9 +1,34 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::{ + rule::{DefaultRuleConfig, Rule}, + utils::{TypeOrValueSpecifier, default_true}, +}; #[derive(Debug, Default, Clone)] -pub struct OnlyThrowError; +pub struct OnlyThrowError(Box); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase", default)] +pub struct OnlyThrowErrorConfig { + /// An array of type or value specifiers for additional types that are allowed to be thrown. + /// Use this to allow throwing custom error types. + pub allow: Vec, + /// Whether to allow throwing values typed as `any`. + #[serde(default = "default_true")] + pub allow_throwing_any: bool, + /// Whether to allow throwing values typed as `unknown`. + #[serde(default = "default_true")] + pub allow_throwing_unknown: bool, +} + +impl Default for OnlyThrowErrorConfig { + fn default() -> Self { + Self { allow: Vec::new(), allow_throwing_any: true, allow_throwing_unknown: true } + } +} declare_oxc_lint!( /// ### What it does @@ -59,6 +84,19 @@ declare_oxc_lint!( typescript, pedantic, pending, + config = OnlyThrowErrorConfig, ); -impl Rule for OnlyThrowError {} +impl Rule for OnlyThrowError { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/rules/typescript/prefer_promise_reject_errors.rs b/crates/oxc_linter/src/rules/typescript/prefer_promise_reject_errors.rs index a1e647e98e997..cba77f74484e0 100644 --- a/crates/oxc_linter/src/rules/typescript/prefer_promise_reject_errors.rs +++ b/crates/oxc_linter/src/rules/typescript/prefer_promise_reject_errors.rs @@ -1,9 +1,22 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::rule::{DefaultRuleConfig, Rule}; #[derive(Debug, Default, Clone)] -pub struct PreferPromiseRejectErrors; +pub struct PreferPromiseRejectErrors(Box); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)] +#[serde(rename_all = "camelCase", default)] +pub struct PreferPromiseRejectErrorsConfig { + /// Whether to allow calling `Promise.reject()` with no arguments. + pub allow_empty_reject: bool, + /// Whether to allow rejecting Promises with values typed as `any`. + pub allow_throwing_any: bool, + /// Whether to allow rejecting Promises with values typed as `unknown`. + pub allow_throwing_unknown: bool, +} declare_oxc_lint!( /// ### What it does @@ -59,6 +72,19 @@ declare_oxc_lint!( typescript, pedantic, pending, + config = PreferPromiseRejectErrorsConfig, ); -impl Rule for PreferPromiseRejectErrors {} +impl Rule for PreferPromiseRejectErrors { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/rules/typescript/promise_function_async.rs b/crates/oxc_linter/src/rules/typescript/promise_function_async.rs index 51b57ed113b40..8439bae4855b3 100644 --- a/crates/oxc_linter/src/rules/typescript/promise_function_async.rs +++ b/crates/oxc_linter/src/rules/typescript/promise_function_async.rs @@ -1,9 +1,50 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::{ + rule::{DefaultRuleConfig, Rule}, + utils::default_true, +}; #[derive(Debug, Default, Clone)] -pub struct PromiseFunctionAsync; +pub struct PromiseFunctionAsync(Box); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase", default)] +pub struct PromiseFunctionAsyncConfig { + /// Whether to allow functions returning `any` type without requiring `async`. + #[serde(default = "default_true")] + pub allow_any: bool, + /// A list of Promise type names that are allowed without requiring `async`. + /// Example: `["SpecialPromise"]` to allow functions returning `SpecialPromise` without `async`. + pub allowed_promise_names: Vec, + /// Whether to check arrow functions for missing `async` keyword. + #[serde(default = "default_true")] + pub check_arrow_functions: bool, + /// Whether to check function declarations for missing `async` keyword. + #[serde(default = "default_true")] + pub check_function_declarations: bool, + /// Whether to check function expressions for missing `async` keyword. + #[serde(default = "default_true")] + pub check_function_expressions: bool, + /// Whether to check method declarations for missing `async` keyword. + #[serde(default = "default_true")] + pub check_method_declarations: bool, +} + +impl Default for PromiseFunctionAsyncConfig { + fn default() -> Self { + Self { + allow_any: true, + allowed_promise_names: Vec::new(), + check_arrow_functions: true, + check_function_declarations: true, + check_function_expressions: true, + check_method_declarations: true, + } + } +} declare_oxc_lint!( /// ### What it does @@ -71,6 +112,19 @@ declare_oxc_lint!( typescript, restriction, pending, + config = PromiseFunctionAsyncConfig, ); -impl Rule for PromiseFunctionAsync {} +impl Rule for PromiseFunctionAsync { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/rules/typescript/restrict_plus_operands.rs b/crates/oxc_linter/src/rules/typescript/restrict_plus_operands.rs index 48e26f281b763..85135bd5ae40f 100644 --- a/crates/oxc_linter/src/rules/typescript/restrict_plus_operands.rs +++ b/crates/oxc_linter/src/rules/typescript/restrict_plus_operands.rs @@ -1,9 +1,49 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::{ + rule::{DefaultRuleConfig, Rule}, + utils::default_true, +}; #[derive(Debug, Default, Clone)] -pub struct RestrictPlusOperands; +pub struct RestrictPlusOperands(Box); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase", default)] +pub struct RestrictPlusOperandsConfig { + /// Whether to allow `any` type in plus operations. + #[serde(default = "default_true")] + pub allow_any: bool, + /// Whether to allow `boolean` types in plus operations. + #[serde(default = "default_true")] + pub allow_boolean: bool, + /// Whether to allow nullish types (`null` or `undefined`) in plus operations. + #[serde(default = "default_true")] + pub allow_nullish: bool, + /// Whether to allow mixed number and string operands in plus operations. + #[serde(default = "default_true")] + pub allow_number_and_string: bool, + /// Whether to allow `RegExp` types in plus operations. + #[serde(default = "default_true")] + pub allow_reg_exp: bool, + /// Whether to skip compound assignments (e.g., `a += b`). + pub skip_compound_assignments: bool, +} + +impl Default for RestrictPlusOperandsConfig { + fn default() -> Self { + Self { + allow_any: true, + allow_boolean: true, + allow_nullish: true, + allow_number_and_string: true, + allow_reg_exp: true, + skip_compound_assignments: false, + } + } +} declare_oxc_lint!( /// ### What it does @@ -61,6 +101,19 @@ declare_oxc_lint!( typescript, pedantic, pending, + config = RestrictPlusOperandsConfig, ); -impl Rule for RestrictPlusOperands {} +impl Rule for RestrictPlusOperands { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/rules/typescript/restrict_template_expressions.rs b/crates/oxc_linter/src/rules/typescript/restrict_template_expressions.rs index 20171017f11e0..1ba2bd59ba180 100644 --- a/crates/oxc_linter/src/rules/typescript/restrict_template_expressions.rs +++ b/crates/oxc_linter/src/rules/typescript/restrict_template_expressions.rs @@ -1,9 +1,68 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::{ + rule::{DefaultRuleConfig, Rule}, + utils::{LibFrom, LibSpecifier, NameSpecifier, TypeOrValueSpecifier, default_true}, +}; + +fn default_restrict_template_allow() -> Vec { + vec![TypeOrValueSpecifier::Lib(LibSpecifier { + from: LibFrom::Lib, + name: NameSpecifier::Multiple(vec![ + "Error".to_string(), + "URL".to_string(), + "URLSearchParams".to_string(), + ]), + })] +} #[derive(Debug, Default, Clone)] -pub struct RestrictTemplateExpressions; +pub struct RestrictTemplateExpressions(Box); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase", default)] +pub struct RestrictTemplateExpressionsConfig { + /// Whether to allow `any` typed values in template expressions. + #[serde(default = "default_true")] + pub allow_any: bool, + /// Whether to allow array types in template expressions. + pub allow_array: bool, + /// Whether to allow boolean types in template expressions. + #[serde(default = "default_true")] + pub allow_boolean: bool, + /// Whether to allow nullish types (`null` or `undefined`) in template expressions. + #[serde(default = "default_true")] + pub allow_nullish: bool, + /// Whether to allow number and bigint types in template expressions. + #[serde(default = "default_true")] + pub allow_number: bool, + /// Whether to allow RegExp values in template expressions. + #[serde(default = "default_true")] + pub allow_reg_exp: bool, + /// Whether to allow `never` type in template expressions. + pub allow_never: bool, + /// An array of type or value specifiers for additional types that are allowed in template expressions. + /// Defaults include Error, URL, and URLSearchParams from lib. + #[serde(default = "default_restrict_template_allow")] + pub allow: Vec, +} + +impl Default for RestrictTemplateExpressionsConfig { + fn default() -> Self { + Self { + allow_any: true, + allow_array: false, + allow_boolean: true, + allow_nullish: true, + allow_number: true, + allow_reg_exp: true, + allow_never: false, + allow: default_restrict_template_allow(), + } + } +} declare_oxc_lint!( /// ### What it does @@ -69,6 +128,19 @@ declare_oxc_lint!( typescript, correctness, pending, + config = RestrictTemplateExpressionsConfig, ); -impl Rule for RestrictTemplateExpressions {} +impl Rule for RestrictTemplateExpressions { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/rules/typescript/return_await.rs b/crates/oxc_linter/src/rules/typescript/return_await.rs index 8c15d8e9395dc..c721699b9bedf 100644 --- a/crates/oxc_linter/src/rules/typescript/return_await.rs +++ b/crates/oxc_linter/src/rules/typescript/return_await.rs @@ -1,9 +1,35 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::rule::{DefaultRuleConfig, Rule}; -#[derive(Debug, Default, Clone)] -pub struct ReturnAwait; +#[derive(Debug, Clone)] +pub struct ReturnAwait(Box); + +impl Default for ReturnAwait { + fn default() -> Self { + Self(Box::new(ReturnAwaitOption::InTryCatch)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)] +#[serde(rename_all = "kebab-case")] +pub enum ReturnAwaitOption { + /// Require `await` when returning Promises inside try/catch/finally blocks. + /// This ensures proper error handling and stack traces. + #[default] + InTryCatch, + /// Require `await` before returning Promises in all cases. + /// Example: `return await Promise.resolve()` is required. + Always, + /// Require `await` only when it affects error handling correctness. + /// Only flags cases where omitting await would change error handling behavior. + ErrorHandlingCorrectnessOnly, + /// Disallow `await` before returning Promises in all cases. + /// Example: `return Promise.resolve()` is required (no await). + Never, +} declare_oxc_lint!( /// ### What it does @@ -71,6 +97,19 @@ declare_oxc_lint!( typescript, pedantic, pending, + config = ReturnAwaitOption, ); -impl Rule for ReturnAwait {} +impl Rule for ReturnAwait { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/rules/typescript/strict_boolean_expressions.rs b/crates/oxc_linter/src/rules/typescript/strict_boolean_expressions.rs index 25eea035f274a..5668e84cfc4d8 100644 --- a/crates/oxc_linter/src/rules/typescript/strict_boolean_expressions.rs +++ b/crates/oxc_linter/src/rules/typescript/strict_boolean_expressions.rs @@ -1,9 +1,57 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::{ + rule::{DefaultRuleConfig, Rule}, + utils::default_true, +}; #[derive(Debug, Default, Clone)] -pub struct StrictBooleanExpressions; +pub struct StrictBooleanExpressions(Box); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase", default)] +pub struct StrictBooleanExpressionsConfig { + /// Whether to allow `any` type in boolean contexts. + pub allow_any: bool, + /// Whether to allow nullable boolean types (e.g., `boolean | null`) in boolean contexts. + pub allow_nullable_boolean: bool, + /// Whether to allow nullable number types (e.g., `number | null`) in boolean contexts. + pub allow_nullable_number: bool, + /// Whether to allow nullable string types (e.g., `string | null`) in boolean contexts. + pub allow_nullable_string: bool, + /// Whether to allow nullable enum types in boolean contexts. + pub allow_nullable_enum: bool, + /// Whether to allow nullable object types in boolean contexts. + #[serde(default = "default_true")] + pub allow_nullable_object: bool, + /// Whether to allow string types in boolean contexts (checks for non-empty strings). + #[serde(default = "default_true")] + pub allow_string: bool, + /// Whether to allow number types in boolean contexts (checks for non-zero numbers). + #[serde(default = "default_true")] + pub allow_number: bool, + /// Whether to allow this rule to run without `strictNullChecks` enabled. + /// This is not recommended as the rule may produce incorrect results. + pub allow_rule_to_run_without_strict_null_checks_i_know_what_i_am_doing: bool, +} + +impl Default for StrictBooleanExpressionsConfig { + fn default() -> Self { + Self { + allow_any: false, + allow_nullable_boolean: false, + allow_nullable_number: false, + allow_nullable_string: false, + allow_nullable_enum: false, + allow_nullable_object: true, + allow_string: true, + allow_number: true, + allow_rule_to_run_without_strict_null_checks_i_know_what_i_am_doing: false, + } + } +} declare_oxc_lint!( /// ### What it does @@ -81,6 +129,19 @@ declare_oxc_lint!( typescript, pedantic, pending, + config = StrictBooleanExpressionsConfig, ); -impl Rule for StrictBooleanExpressions {} +impl Rule for StrictBooleanExpressions { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/rules/typescript/switch_exhaustiveness_check.rs b/crates/oxc_linter/src/rules/typescript/switch_exhaustiveness_check.rs index 9138495fb7256..5bb0572c1808f 100644 --- a/crates/oxc_linter/src/rules/typescript/switch_exhaustiveness_check.rs +++ b/crates/oxc_linter/src/rules/typescript/switch_exhaustiveness_check.rs @@ -1,9 +1,30 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::rule::{DefaultRuleConfig, Rule}; #[derive(Debug, Default, Clone)] -pub struct SwitchExhaustivenessCheck; +pub struct SwitchExhaustivenessCheck(Box); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)] +#[serde(rename_all = "camelCase", default)] +pub struct SwitchExhaustivenessCheckConfig { + /// Whether to allow default cases on switches that are not exhaustive. + /// When false, requires exhaustive switch statements without default cases. + pub allow_default_case_for_exhaustive_switch: bool, + /// Whether to allow this rule to run without `strictNullChecks` enabled. + /// This is not recommended as the rule may produce incorrect results. + pub allow_rule_to_run_without_strict_null_checks_i_know_what_i_am_doing: bool, + /// Regular expression pattern that when matched in a default case comment, + /// will suppress the exhaustiveness check. + /// Example: `"@skip-exhaustive-check"` to allow `default: // @skip-exhaustive-check` + #[serde(skip_serializing_if = "Option::is_none")] + pub default_case_comment_pattern: Option, + /// Whether to require default cases on switches over union types that are not exhaustive. + /// When true, switches with non-exhaustive union types must have a default case. + pub require_default_for_non_union: bool, +} declare_oxc_lint!( /// ### What it does @@ -101,6 +122,19 @@ declare_oxc_lint!( typescript, pedantic, pending, + config = SwitchExhaustivenessCheckConfig, ); -impl Rule for SwitchExhaustivenessCheck {} +impl Rule for SwitchExhaustivenessCheck { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/rules/typescript/unbound_method.rs b/crates/oxc_linter/src/rules/typescript/unbound_method.rs index 7268927cf2cf0..1f78221b4eaa2 100644 --- a/crates/oxc_linter/src/rules/typescript/unbound_method.rs +++ b/crates/oxc_linter/src/rules/typescript/unbound_method.rs @@ -1,9 +1,19 @@ use oxc_macros::declare_oxc_lint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; -use crate::rule::Rule; +use crate::rule::{DefaultRuleConfig, Rule}; #[derive(Debug, Default, Clone)] -pub struct UnboundMethod; +pub struct UnboundMethod(Box); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)] +#[serde(rename_all = "camelCase", default)] +pub struct UnboundMethodConfig { + /// Whether to ignore unbound methods that are static. + /// When true, static methods can be referenced without binding. + pub ignore_static: bool, +} declare_oxc_lint!( /// ### What it does @@ -87,6 +97,19 @@ declare_oxc_lint!( typescript, correctness, pending, + config = UnboundMethodConfig, ); -impl Rule for UnboundMethod {} +impl Rule for UnboundMethod { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new( + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner(), + )) + } + + fn to_configuration(&self) -> Option> { + Some(serde_json::to_value(&*self.0)) + } +} diff --git a/crates/oxc_linter/src/utils/mod.rs b/crates/oxc_linter/src/utils/mod.rs index ef929aea5bc3d..5dd50857b9951 100644 --- a/crates/oxc_linter/src/utils/mod.rs +++ b/crates/oxc_linter/src/utils/mod.rs @@ -19,6 +19,7 @@ mod promise; mod react; mod react_perf; mod regex; +mod typescript; mod unicorn; mod url; mod vitest; @@ -26,7 +27,7 @@ mod vue; pub use self::{ comment::*, config::*, express::*, jest::*, jsdoc::*, nextjs::*, promise::*, react::*, - react_perf::*, regex::*, unicorn::*, url::*, vitest::*, vue::*, + react_perf::*, regex::*, typescript::*, unicorn::*, url::*, vitest::*, vue::*, }; /// List of Jest rules that have Vitest equivalents. diff --git a/crates/oxc_linter/src/utils/typescript.rs b/crates/oxc_linter/src/utils/typescript.rs new file mode 100644 index 0000000000000..324e68d965534 --- /dev/null +++ b/crates/oxc_linter/src/utils/typescript.rs @@ -0,0 +1,109 @@ +//! Shared TypeScript utilities and types for linter rules + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Type or value specifier for matching specific declarations +/// +/// Supports four types of specifiers: +/// +/// 1. **String specifier** (deprecated): Universal match by name +/// ```json +/// "Promise" +/// ``` +/// +/// 2. **File specifier**: Match types/values declared in local files +/// ```json +/// { "from": "file", "name": "MyType" } +/// { "from": "file", "name": ["Type1", "Type2"] } +/// { "from": "file", "name": "MyType", "path": "./types.ts" } +/// ``` +/// +/// 3. **Lib specifier**: Match TypeScript built-in lib types +/// ```json +/// { "from": "lib", "name": "Promise" } +/// { "from": "lib", "name": ["Promise", "PromiseLike"] } +/// ``` +/// +/// 4. **Package specifier**: Match types/values from npm packages +/// ```json +/// { "from": "package", "name": "Observable", "package": "rxjs" } +/// { "from": "package", "name": ["Observable", "Subject"], "package": "rxjs" } +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum TypeOrValueSpecifier { + /// Universal string specifier - matches all types and values with this name regardless of declaration source. + /// Not recommended - will be removed in a future major version. + String(String), + /// Describes specific types or values declared in local files. + File(FileSpecifier), + /// Describes specific types or values declared in TypeScript's built-in lib.*.d.ts types. + Lib(LibSpecifier), + /// Describes specific types or values imported from packages. + Package(PackageSpecifier), +} + +/// Describes specific types or values declared in local files. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct FileSpecifier { + /// Must be "file" + pub from: FileFrom, + /// The name(s) of the type or value to match + pub name: NameSpecifier, + /// Optional file path to specify where the types or values must be declared. + /// If omitted, all files will be matched. + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum FileFrom { + File, +} + +/// Describes specific types or values declared in TypeScript's built-in lib.*.d.ts types. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct LibSpecifier { + /// Must be "lib" + pub from: LibFrom, + /// The name(s) of the lib type or value to match + pub name: NameSpecifier, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum LibFrom { + Lib, +} + +/// Describes specific types or values imported from packages. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct PackageSpecifier { + /// Must be "package" + pub from: PackageFrom, + /// The name(s) of the type or value to match + pub name: NameSpecifier, + /// The package name to match + pub package: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum PackageFrom { + Package, +} + +/// Name specifier that can be a single string or array of strings +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum NameSpecifier { + /// Single name + Single(String), + /// Multiple names + Multiple(Vec), +}