diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 6be8440bb76f..41950c9b6271 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -40,6 +40,7 @@ mod eslint { pub mod array_callback_return; pub mod constructor_super; pub mod default_case_last; + pub mod default_param_last; pub mod eqeqeq; pub mod for_direction; pub mod getter_return; @@ -362,6 +363,7 @@ oxc_macros::declare_all_lint_rules! { eslint::array_callback_return, eslint::constructor_super, eslint::default_case_last, + eslint::default_param_last, eslint::eqeqeq, eslint::for_direction, eslint::getter_return, diff --git a/crates/oxc_linter/src/rules/eslint/default_param_last.rs b/crates/oxc_linter/src/rules/eslint/default_param_last.rs new file mode 100644 index 000000000000..529e7d2f67b1 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/default_param_last.rs @@ -0,0 +1,104 @@ +use oxc_ast::ast::FormalParameter; +use oxc_ast::AstKind; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint(default-param-last): Default parameters should be last")] +#[diagnostic(severity(warning), help("Enforce default parameters to be last."))] +struct DefaultParamLastDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct DefaultParamLast; + +declare_oxc_lint!( + /// ### What it does + /// Enforce default parameters to be last + /// + /// ### Why is this bad? + /// Putting default parameter at last allows function calls to omit optional tail arguments. + /// + /// ### Example + /// ```javascript + /// // Correct: optional argument can be omitted + /// function createUser(id, isAdmin = false) {} + /// createUser("tabby") + /// + /// // Incorrect: optional argument can **not** be omitted + /// function createUser(isAdmin = false, id) {} + /// createUser(undefined, "tabby") + /// ``` + DefaultParamLast, + style +); + +impl Rule for DefaultParamLast { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::Function(function) => { + if !function.is_declaration() && !function.is_expression() { + return; + } + check_params(&function.params.items, ctx); + } + AstKind::ArrowFunctionExpression(function) => check_params(&function.params.items, ctx), + _ => {} + } + } +} + +fn check_params<'a>(items: &'a [FormalParameter<'a>], ctx: &LintContext<'a>) { + let mut has_seen_plain_param = false; + for param in items.iter().rev() { + if !param.pattern.kind.is_assignment_pattern() { + has_seen_plain_param = true; + continue; + } + if has_seen_plain_param && param.pattern.kind.is_assignment_pattern() { + ctx.diagnostic(DefaultParamLastDiagnostic(param.span)); + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "function f() {}", + "function f(a) {}", + "function f(a = 5) {}", + "function f(a, b) {}", + "function f(a, b = 5) {}", + "function f(a, b = 5, c = 5) {}", + "function f(a, b = 5, ...c) {}", + "const f = () => {}", + "const f = (a) => {}", + "const f = (a = 5) => {}", + "const f = function f() {}", + "const f = function f(a) {}", + "const f = function f(a = 5) {}", + ]; + + let fail = vec![ + "function f(a = 5, b) {}", + "function f(a = 5, b = 6, c) {}", + "function f (a = 5, b, c = 6, d) {}", + "function f(a = 5, b, c = 5) {}", + "const f = (a = 5, b, ...c) => {}", + "const f = function f (a, b = 5, c) {}", + "const f = (a = 5, { b }) => {}", + "const f = ({ a } = {}, b) => {}", + "const f = ({ a, b } = { a: 1, b: 2 }, c) => {}", + "const f = ([a] = [], b) => {}", + "const f = ([a, b] = [1, 2], c) => {}", + ]; + + Tester::new(DefaultParamLast::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/default_param_last.snap b/crates/oxc_linter/src/snapshots/default_param_last.snap new file mode 100644 index 000000000000..3274e5596381 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/default_param_last.snap @@ -0,0 +1,94 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: default_param_last +--- + ⚠ eslint(default-param-last): Default parameters should be last + ╭─[default_param_last.tsx:1:12] + 1 │ function f(a = 5, b) {} + · ───── + ╰──── + help: Enforce default parameters to be last. + + ⚠ eslint(default-param-last): Default parameters should be last + ╭─[default_param_last.tsx:1:19] + 1 │ function f(a = 5, b = 6, c) {} + · ───── + ╰──── + help: Enforce default parameters to be last. + + ⚠ eslint(default-param-last): Default parameters should be last + ╭─[default_param_last.tsx:1:12] + 1 │ function f(a = 5, b = 6, c) {} + · ───── + ╰──── + help: Enforce default parameters to be last. + + ⚠ eslint(default-param-last): Default parameters should be last + ╭─[default_param_last.tsx:1:23] + 1 │ function f (a = 5, b, c = 6, d) {} + · ───── + ╰──── + help: Enforce default parameters to be last. + + ⚠ eslint(default-param-last): Default parameters should be last + ╭─[default_param_last.tsx:1:13] + 1 │ function f (a = 5, b, c = 6, d) {} + · ───── + ╰──── + help: Enforce default parameters to be last. + + ⚠ eslint(default-param-last): Default parameters should be last + ╭─[default_param_last.tsx:1:12] + 1 │ function f(a = 5, b, c = 5) {} + · ───── + ╰──── + help: Enforce default parameters to be last. + + ⚠ eslint(default-param-last): Default parameters should be last + ╭─[default_param_last.tsx:1:12] + 1 │ const f = (a = 5, b, ...c) => {} + · ───── + ╰──── + help: Enforce default parameters to be last. + + ⚠ eslint(default-param-last): Default parameters should be last + ╭─[default_param_last.tsx:1:26] + 1 │ const f = function f (a, b = 5, c) {} + · ───── + ╰──── + help: Enforce default parameters to be last. + + ⚠ eslint(default-param-last): Default parameters should be last + ╭─[default_param_last.tsx:1:12] + 1 │ const f = (a = 5, { b }) => {} + · ───── + ╰──── + help: Enforce default parameters to be last. + + ⚠ eslint(default-param-last): Default parameters should be last + ╭─[default_param_last.tsx:1:12] + 1 │ const f = ({ a } = {}, b) => {} + · ────────── + ╰──── + help: Enforce default parameters to be last. + + ⚠ eslint(default-param-last): Default parameters should be last + ╭─[default_param_last.tsx:1:12] + 1 │ const f = ({ a, b } = { a: 1, b: 2 }, c) => {} + · ───────────────────────── + ╰──── + help: Enforce default parameters to be last. + + ⚠ eslint(default-param-last): Default parameters should be last + ╭─[default_param_last.tsx:1:12] + 1 │ const f = ([a] = [], b) => {} + · ──────── + ╰──── + help: Enforce default parameters to be last. + + ⚠ eslint(default-param-last): Default parameters should be last + ╭─[default_param_last.tsx:1:12] + 1 │ const f = ([a, b] = [1, 2], c) => {} + · ─────────────── + ╰──── + help: Enforce default parameters to be last.