mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter): eslint-plugin-unicorn prefer-dom-node-text-content(style) (#1658)
Implement [prefer-dom-node-text-content](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-dom-node-text-content.md) for #684 .
This commit is contained in:
parent
00806384ff
commit
282771afc4
3 changed files with 242 additions and 0 deletions
|
|
@ -196,6 +196,7 @@ mod unicorn {
|
|||
pub mod prefer_dom_node_append;
|
||||
pub mod prefer_dom_node_dataset;
|
||||
pub mod prefer_dom_node_remove;
|
||||
pub mod prefer_dom_node_text_content;
|
||||
pub mod prefer_event_target;
|
||||
pub mod prefer_includes;
|
||||
pub mod prefer_logical_operator_over_ternary;
|
||||
|
|
@ -393,6 +394,7 @@ oxc_macros::declare_all_lint_rules! {
|
|||
unicorn::prefer_dom_node_append,
|
||||
unicorn::prefer_dom_node_dataset,
|
||||
unicorn::prefer_dom_node_remove,
|
||||
unicorn::prefer_dom_node_text_content,
|
||||
unicorn::prefer_event_target,
|
||||
unicorn::prefer_includes,
|
||||
unicorn::prefer_logical_operator_over_ternary,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
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, Fix};
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error(
|
||||
"eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`."
|
||||
)]
|
||||
#[diagnostic(severity(warning), help("Replace `.innerText` with `.textContent`."))]
|
||||
struct PreferDomNodeTextContentDiagnostic(#[label] pub Span);
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct PreferDomNodeTextContent;
|
||||
|
||||
declare_oxc_lint!(
|
||||
/// ### What it does
|
||||
///
|
||||
/// Enforces the use of `.textContent` over `.innerText` for DOM nodes.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// There are some disadvantages of using .innerText.
|
||||
/// - `.innerText` is much more performance-heavy as it requires layout information to return the result.
|
||||
/// - `.innerText` is defined only for HTMLElement objects, while `.textContent` is defined for all Node objects.
|
||||
/// - `.innerText` is not standard, for example, it is not present in Firefox.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```javascript
|
||||
/// // Bad
|
||||
/// const text = foo.innerText;
|
||||
///
|
||||
/// // Good
|
||||
/// const text = foo.textContent;
|
||||
/// ```
|
||||
PreferDomNodeTextContent,
|
||||
style
|
||||
);
|
||||
|
||||
impl Rule for PreferDomNodeTextContent {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::MemberExpression(member_expr) = node.kind() {
|
||||
if let Some((span, name)) = member_expr.static_property_info() {
|
||||
if name == "innerText" && !member_expr.is_computed() {
|
||||
ctx.diagnostic_with_fix(PreferDomNodeTextContentDiagnostic(span), || {
|
||||
Fix::new("textContent", span)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let Some(parent_node) = ctx.nodes().parent_node(node.id()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(grand_parent_node) = ctx.nodes().parent_node(parent_node.id()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let parent_node_kind = parent_node.kind();
|
||||
let grand_parent_node_kind = grand_parent_node.kind();
|
||||
|
||||
// `const {innerText} = node` or `({innerText: text} = node)`
|
||||
if let AstKind::IdentifierName(identifier) = node.kind() {
|
||||
if identifier.name == "innerText"
|
||||
&& matches!(parent_node_kind, AstKind::PropertyKey(_))
|
||||
&& (matches!(grand_parent_node_kind, AstKind::ObjectPattern(_))
|
||||
|| matches!(grand_parent_node_kind, AstKind::AssignmentTarget(_)))
|
||||
{
|
||||
ctx.diagnostic(PreferDomNodeTextContentDiagnostic(identifier.span));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// `({innerText} = node)`
|
||||
if let AstKind::IdentifierReference(identifier_ref) = node.kind() {
|
||||
if identifier_ref.name == "innerText"
|
||||
&& matches!(parent_node_kind, AstKind::AssignmentTarget(_))
|
||||
&& matches!(grand_parent_node_kind, AstKind::AssignmentExpression(_))
|
||||
{
|
||||
ctx.diagnostic(PreferDomNodeTextContentDiagnostic(identifier_ref.span));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
use crate::tester::Tester;
|
||||
|
||||
let pass = vec![
|
||||
("innerText;", None),
|
||||
("node.textContent;", None),
|
||||
("node[innerText];", None),
|
||||
("innerText = true;", None),
|
||||
("node['innerText'];", None),
|
||||
("innerText.textContent", None),
|
||||
("const [innerText] = node;", None),
|
||||
("[innerText] = node;", None),
|
||||
("const {[innerText]: text} = node;", None),
|
||||
("({[innerText]: text} = node);", None),
|
||||
("const foo = {innerText}", None),
|
||||
("const foo = {innerText: text}", None),
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
("node.innerText;", None),
|
||||
("node?.innerText;", None),
|
||||
("node.innerText = 'foo';", None),
|
||||
("innerText.innerText;", None),
|
||||
("const {innerText} = node;", None),
|
||||
("const {innerText,} = node;", None),
|
||||
("const {innerText: text} = node;", None),
|
||||
("const {innerText = \"default text\"} = node;", None),
|
||||
("const {innerText: text = \"default text\"} = node;", None),
|
||||
("({innerText} = node);", None),
|
||||
("({innerText: text} = node);", None),
|
||||
("({innerText = \"default text\"} = node);", None),
|
||||
("({innerText: text = \"default text\"} = node);", None),
|
||||
("function foo({innerText}) {return innerText}", None),
|
||||
("for (const [{innerText}] of elements);", None),
|
||||
];
|
||||
|
||||
Tester::new(PreferDomNodeTextContent::NAME, pass, fail).test_and_snapshot();
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
---
|
||||
source: crates/oxc_linter/src/tester.rs
|
||||
expression: prefer_dom_node_text_content
|
||||
---
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ node.innerText;
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ node?.innerText;
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ node.innerText = 'foo';
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ innerText.innerText;
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ const {innerText} = node;
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ const {innerText,} = node;
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ const {innerText: text} = node;
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ const {innerText = "default text"} = node;
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ const {innerText: text = "default text"} = node;
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ ({innerText} = node);
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ ({innerText: text} = node);
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ ({innerText = "default text"} = node);
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ ({innerText: text = "default text"} = node);
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ function foo({innerText}) {return innerText}
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
⚠ eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.
|
||||
╭─[prefer_dom_node_text_content.tsx:1:1]
|
||||
1 │ for (const [{innerText}] of elements);
|
||||
· ─────────
|
||||
╰────
|
||||
help: Replace `.innerText` with `.textContent`.
|
||||
|
||||
|
||||
Loading…
Reference in a new issue