From 6f642e2a9fb7e32e63b630a05ec45676cdc6ed82 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 13 Nov 2023 00:58:06 +0000 Subject: [PATCH] feat(linter) eslint plugin unicorn: prefer dom node append (#1256) --- crates/oxc_linter/src/rules.rs | 2 + .../rules/unicorn/prefer_dom_node_append.rs | 129 ++++++++++++++++ .../src/snapshots/prefer_dom_node_append.snap | 145 ++++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 crates/oxc_linter/src/rules/unicorn/prefer_dom_node_append.rs create mode 100644 crates/oxc_linter/src/snapshots/prefer_dom_node_append.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 04d61989d..3007ffd34 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -165,6 +165,7 @@ mod unicorn { pub mod prefer_blob_reading_methods; pub mod prefer_code_point; pub mod prefer_date_now; + pub mod prefer_dom_node_append; pub mod prefer_logical_operator_over_ternary; pub mod prefer_optional_catch_binding; pub mod prefer_query_selector; @@ -309,6 +310,7 @@ oxc_macros::declare_all_lint_rules! { unicorn::prefer_blob_reading_methods, unicorn::prefer_code_point, unicorn::prefer_date_now, + unicorn::prefer_dom_node_append, unicorn::prefer_logical_operator_over_ternary, unicorn::prefer_optional_catch_binding, unicorn::prefer_query_selector, diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_dom_node_append.rs b/crates/oxc_linter/src/rules/unicorn/prefer_dom_node_append.rs new file mode 100644 index 000000000..e3349db49 --- /dev/null +++ b/crates/oxc_linter/src/rules/unicorn/prefer_dom_node_append.rs @@ -0,0 +1,129 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use oxc_ast::ast::MemberExpression; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes.")] +#[diagnostic(severity(warning), help("Replace `Node#appendChild()` with `Node#append()`."))] +struct PreferDomNodeAppendDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct PreferDomNodeAppend; + +declare_oxc_lint!( + /// ### What it does + /// + ///Enforces the use of, for example, `document.body.append(div);` over `document.body.appendChild(div);` for DOM nodes. + /// + /// ### Why is this bad? + /// + /// There are [some advantages of using `Node#append()`](https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append), like the ability to append multiple nodes and to append both [`DOMString`](https://developer.mozilla.org/en-US/docs/Web/API/DOMString) and DOM node objects. + /// + /// ### Example + /// ```javascript + /// // bad + /// foo.appendChild(bar); + /// + // // good + /// foo.append(bar); + // + /// ``` + PreferDomNodeAppend, + correctness +); + +impl Rule for PreferDomNodeAppend { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + // if ( + // !isMethodCall(node, { + // method: 'appendChild', + // argumentsLength: 1, + // optionalCall: false, + // }) + // || isNodeValueNotDomNode(node.callee.object) + // || isNodeValueNotDomNode(node.arguments[0]) + // ) { + // return; + // } + + let AstKind::CallExpression(call_expr) = node.kind() else { + return; + }; + + if call_expr.optional { + return; + } + + let Some(member_expr) = call_expr.callee.get_member_expr() else { return }; + + let span = match member_expr { + MemberExpression::StaticMemberExpression(v) => { + if !matches!(v.property.name.as_str(), "appendChild") { + return; + } + v.property.span + } + _ => return, + }; + + if call_expr.arguments.len() != 1 { + return; + } + + if call_expr.arguments[0].is_spread() { + return; + } + + ctx.diagnostic(PreferDomNodeAppendDiagnostic(span)); + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + r#"parent.append(child);"#, + r#"new parent.appendChild(child);"#, + r#"appendChild(child);"#, + r#"parent['appendChild'](child);"#, + r#"parent[appendChild](child);"#, + r#"parent.foo(child);"#, + r#"parent.appendChild(one, two);"#, + r#"parent.appendChild();"#, + r#"parent.appendChild(...argumentsArray)"#, + r#"parent.appendChild?.(child)"#, + ]; + + let fail = vec![ + r#"node.appendChild(child);"#, + r#"document.body.appendChild(child);"#, + r#"node.appendChild(foo)"#, + r#"const foo = node.appendChild(child);"#, + r#"console.log(node.appendChild(child));"#, + r#"node.appendChild(child).appendChild(grandchild);"#, + r#"node.appendChild(child) || "foo";"#, + r#"node.appendChild(child) + 0;"#, + r#"node.appendChild(child) + 0;"#, + r#"+node.appendChild(child);"#, + r#"node.appendChild(child) ? "foo" : "bar";"#, + r#"if (node.appendChild(child)) {}"#, + r#"const foo = [node.appendChild(child)]"#, + r#"const foo = { bar: node.appendChild(child) }"#, + r#"function foo() { return node.appendChild(child); }"#, + r#"const foo = () => { return node.appendChild(child); }"#, + r#"foo(bar = node.appendChild(child))"#, + r#"node?.appendChild(child);"#, + r#"() => node?.appendChild(child)"#, + ]; + + Tester::new_without_config(PreferDomNodeAppend::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/prefer_dom_node_append.snap b/crates/oxc_linter/src/snapshots/prefer_dom_node_append.snap new file mode 100644 index 000000000..fde354712 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/prefer_dom_node_append.snap @@ -0,0 +1,145 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: prefer_dom_node_append +--- + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ node.appendChild(child); + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ document.body.appendChild(child); + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ node.appendChild(foo) + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ const foo = node.appendChild(child); + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ console.log(node.appendChild(child)); + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ node.appendChild(child).appendChild(grandchild); + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ node.appendChild(child).appendChild(grandchild); + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ node.appendChild(child) || "foo"; + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ node.appendChild(child) + 0; + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ node.appendChild(child) + 0; + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ +node.appendChild(child); + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ node.appendChild(child) ? "foo" : "bar"; + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ if (node.appendChild(child)) {} + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ const foo = [node.appendChild(child)] + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ const foo = { bar: node.appendChild(child) } + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ function foo() { return node.appendChild(child); } + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ const foo = () => { return node.appendChild(child); } + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ foo(bar = node.appendChild(child)) + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ node?.appendChild(child); + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + + ⚠ eslint-plugin-unicorn(prefer-dom-node-append): Prefer `Node#append()` over `Node#appendChild()` for DOM nodes. + ╭─[prefer_dom_node_append.tsx:1:1] + 1 │ () => node?.appendChild(child) + · ─────────── + ╰──── + help: Replace `Node#appendChild()` with `Node#append()`. + +