Skip to content

Commit

Permalink
feat(linter): eslint-plugin-unicorn no-object-as-default-parameter (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
camc314 committed Nov 6, 2023
1 parent 167dedc commit a5b87c4
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 2 deletions.
6 changes: 4 additions & 2 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ mod unicorn {
pub mod no_instanceof_array;
pub mod no_invalid_remove_event_listener;
pub mod no_new_array;
pub mod no_object_as_default_parameter;
pub mod no_thenable;
pub mod no_unnecessary_await;
pub mod prefer_array_flat_map;
Expand Down Expand Up @@ -276,18 +277,19 @@ oxc_macros::declare_all_lint_rules! {
unicorn::no_instanceof_array,
unicorn::no_invalid_remove_event_listener,
unicorn::no_new_array,
unicorn::no_object_as_default_parameter,
unicorn::no_thenable,
unicorn::no_unnecessary_await,
unicorn::prefer_array_flat_map,
unicorn::prefer_date_now,
unicorn::prefer_logical_operator_over_ternary,
unicorn::prefer_query_selector,
unicorn::prefer_string_trim_start_end,
unicorn::prefer_type_error,
unicorn::require_number_to_fixed_digits_argument,
unicorn::switch_case_braces,
unicorn::text_encoding_identifier_case,
unicorn::throw_new_error,
unicorn::prefer_string_trim_start_end,
unicorn::prefer_query_selector,
react::jsx_key,
react::jsx_no_comment_text_nodes,
react::jsx_no_duplicate_props,
Expand Down
132 changes: 132 additions & 0 deletions crates/oxc_linter/src/rules/unicorn/no_object_as_default_parameter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use oxc_ast::{
ast::{BindingPatternKind, Expression},
AstKind,
};
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::{self, Error},
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

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

#[derive(Debug, Error, Diagnostic)]
enum NoObjectAsDefaultParameterDiagnostic {
#[error("eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default for parameter `{1}`.")]
#[diagnostic(severity(warning))]
Identifier(#[label] Span, String),
#[error("eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default")]
#[diagnostic(severity(warning))]
NonIdentifier(#[label] Span),
}

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

declare_oxc_lint!(
/// ### What it does
///
/// Disallow the use of an object literal as a default value for a parameter.
///
/// ### Why is this bad?
///
/// Default parameters should not be passed to a function through an object literal. The `foo = {a: false}` parameter works fine if only used with one option. As soon as additional options are added, you risk replacing the whole `foo = {a: false, b: true}` object when passing only one option: `{a: true}`. For this reason, object destructuring should be used instead.
///
/// ### Example
/// ```javascript
/// // Bad
/// function foo(foo = {a: false}) {}
///
/// // Good
/// function foo({a = false} = {}) {}
/// ```
NoObjectAsDefaultParameter,
pedantic
);

impl Rule for NoObjectAsDefaultParameter {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::AssignmentPattern(assignment_pat) = node.kind() else { return };

let Expression::ObjectExpression(object_expr) = &assignment_pat.right else {
return;
};

if object_expr.properties.len() == 0 {
return;
}

let Some(parent) = ctx.nodes().parent_node(node.id()) else { return };

if !matches!(parent.kind(), AstKind::FormalParameter(_)) {
return;
}

if let BindingPatternKind::BindingIdentifier(binding_id) = &assignment_pat.left.kind {
ctx.diagnostic(NoObjectAsDefaultParameterDiagnostic::Identifier(
object_expr.span,
binding_id.name.to_string(),
));
return;
}

ctx.diagnostic(NoObjectAsDefaultParameterDiagnostic::NonIdentifier(object_expr.span));
}
}

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

let pass = vec![
r#"const abc = {};"#,
r#"const abc = {foo: 123};"#,
r#"function abc(foo) {}"#,
r#"function abc(foo = null) {}"#,
r#"function abc(foo = undefined) {}"#,
r#"function abc(foo = 123) {}"#,
r#"function abc(foo = true) {}"#,
r#"function abc(foo = "bar") {}"#,
r#"function abc(foo = 123, bar = "foo") {}"#,
r#"function abc(foo = {}) {}"#,
r#"function abc({foo = 123} = {}) {}"#,
r#"(function abc() {})(foo = {a: 123})"#,
r#"const abc = foo => {};"#,
r#"const abc = (foo = null) => {};"#,
r#"const abc = (foo = undefined) => {};"#,
r#"const abc = (foo = 123) => {};"#,
r#"const abc = (foo = true) => {};"#,
r#"const abc = (foo = "bar") => {};"#,
r#"const abc = (foo = 123, bar = "foo") => {};"#,
r#"const abc = (foo = {}) => {};"#,
r#"const abc = ({a = true, b = "foo"}) => {};"#,
r#"const abc = function(foo = 123) {}"#,
r#"const {abc = {foo: 123}} = bar;"#,
r#"const {abc = {null: "baz"}} = bar;"#,
r#"const {abc = {foo: undefined}} = undefined;"#,
r#"const abc = ([{foo = false, bar = 123}]) => {};"#,
r#"const abc = ({foo = {a: 123}}) => {};"#,
r#"const abc = ([foo = {a: 123}]) => {};"#,
r#"const abc = ({foo: bar = {a: 123}}) => {};"#,
r#"const abc = () => (foo = {a: 123});"#,
];

let fail = vec![
r#"function abc(foo = {a: 123}) {}"#,
r#"async function * abc(foo = {a: 123}) {}"#,
r#"function abc(foo = {a: false}) {}"#,
r#"function abc(foo = {a: "bar"}) {}"#,
r#"function abc(foo = {a: "bar", b: {c: true}}) {}"#,
r#"const abc = (foo = {a: false}) => {};"#,
r#"const abc = (foo = {a: 123, b: false}) => {};"#,
r#"const abc = (foo = {a: false, b: 1, c: "test", d: null}) => {};"#,
r#"const abc = function(foo = {a: 123}) {}"#,
r#"function abc(foo = {a: 123}) {}"#,
r#"const abc = (foo = {a: false}) => {};"#,
r#"function abc({a} = {a: 123}) {}"#,
r#"function abc([a] = {a: 123}) {}"#,
];

Tester::new_without_config(NoObjectAsDefaultParameter::NAME, pass, fail).test_and_snapshot();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
source: crates/oxc_linter/src/tester.rs
expression: no_object_as_default_parameter
---
eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default for parameter `foo`.
╭─[no_object_as_default_parameter.tsx:1:1]
1function abc(foo = {a: 123}) {}
· ────────
╰────

eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default for parameter `foo`.
╭─[no_object_as_default_parameter.tsx:1:1]
1async function * abc(foo = {a: 123}) {}
· ────────
╰────

eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default for parameter `foo`.
╭─[no_object_as_default_parameter.tsx:1:1]
1function abc(foo = {a: false}) {}
· ──────────
╰────

eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default for parameter `foo`.
╭─[no_object_as_default_parameter.tsx:1:1]
1function abc(foo = {a: "bar"}) {}
· ──────────
╰────

eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default for parameter `foo`.
╭─[no_object_as_default_parameter.tsx:1:1]
1function abc(foo = {a: "bar", b: {c: true}}) {}
· ────────────────────────
╰────

eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default for parameter `foo`.
╭─[no_object_as_default_parameter.tsx:1:1]
1const abc = (foo = {a: false}) => {};
· ──────────
╰────

eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default for parameter `foo`.
╭─[no_object_as_default_parameter.tsx:1:1]
1const abc = (foo = {a: 123, b: false}) => {};
· ──────────────────
╰────

eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default for parameter `foo`.
╭─[no_object_as_default_parameter.tsx:1:1]
1const abc = (foo = {a: false, b: 1, c: "test", d: null}) => {};
· ────────────────────────────────────
╰────

eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default for parameter `foo`.
╭─[no_object_as_default_parameter.tsx:1:1]
1const abc = function(foo = {a: 123}) {}
· ────────
╰────

eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default for parameter `foo`.
╭─[no_object_as_default_parameter.tsx:1:1]
1function abc(foo = {a: 123}) {}
· ────────
╰────

eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default for parameter `foo`.
╭─[no_object_as_default_parameter.tsx:1:1]
1const abc = (foo = {a: false}) => {};
· ──────────
╰────

eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default
╭─[no_object_as_default_parameter.tsx:1:1]
1function abc({a} = {a: 123}) {}
· ────────
╰────

eslint-plugin-unicorn(no-object-as-default-parameter): Do not use an object literal as default
╭─[no_object_as_default_parameter.tsx:1:1]
1function abc([a] = {a: 123}) {}
· ────────
╰────


0 comments on commit a5b87c4

Please sign in to comment.