From 5cf8543be1286086524d0f9dd84f4caeb761f4c5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 6 Nov 2023 12:26:56 +0000 Subject: [PATCH] feat(linter) eslint-plugin-unicorn: prefer code point (#1167) --- crates/oxc_linter/src/rules.rs | 2 + .../src/rules/unicorn/prefer_code_point.rs | 100 ++++++++++++++++++ .../src/snapshots/prefer_code_point.snap | 33 ++++++ 3 files changed, 135 insertions(+) create mode 100644 crates/oxc_linter/src/rules/unicorn/prefer_code_point.rs create mode 100644 crates/oxc_linter/src/snapshots/prefer_code_point.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 64393b75b..eba3556b5 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -153,6 +153,7 @@ mod unicorn { pub mod no_thenable; pub mod no_unnecessary_await; pub mod prefer_array_flat_map; + pub mod prefer_code_point; pub mod prefer_date_now; pub mod prefer_logical_operator_over_ternary; pub mod prefer_query_selector; @@ -283,6 +284,7 @@ oxc_macros::declare_all_lint_rules! { unicorn::no_thenable, unicorn::no_unnecessary_await, unicorn::prefer_array_flat_map, + unicorn::prefer_code_point, unicorn::prefer_date_now, unicorn::prefer_logical_operator_over_ternary, unicorn::prefer_query_selector, diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_code_point.rs b/crates/oxc_linter/src/rules/unicorn/prefer_code_point.rs new file mode 100644 index 000000000..01249230d --- /dev/null +++ b/crates/oxc_linter/src/rules/unicorn/prefer_code_point.rs @@ -0,0 +1,100 @@ +use oxc_ast::{ast::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)] +#[error("eslint-plugin-unicorn(prefer-code-point): Prefer `{1}` over `{2}`")] +#[diagnostic(severity(warning), help("Unicode is better supported in `{1}` than `{2}`"))] +struct PreferCodePointDiagnostic(#[label] pub Span, pub &'static str, pub &'static str); + +#[derive(Debug, Default, Clone)] +pub struct PreferCodePoint; + +declare_oxc_lint!( + /// ### What it does + /// + /// Prefers usage of `String.prototype.codePointAt` over `String.prototype.charCodeAt`. + /// Prefers usage of `String.fromCodePoint` over `String.fromCharCode`. + /// + /// ### Why is this bad? + /// + /// Unicode is better supported in [`String#codePointAt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) and [`String.fromCodePoint()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint). + /// + /// [Difference between `String.fromCodePoint()` and `String.fromCharCode()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint#compared_to_fromcharcode) + /// + /// ### Example + /// ```javascript + /// // bad + /// '๐Ÿฆ„'.charCodeAt(0); + /// String.fromCharCode(0x1f984); + /// + /// // good + /// '๐Ÿฆ„'.codePointAt(0); + /// String.fromCodePoint(0x1f984); + /// ``` + PreferCodePoint, + pedantic +); + +impl Rule for PreferCodePoint { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::CallExpression(call_expr) = node.kind() else { return }; + + let Expression::MemberExpression(memb_expr) = &call_expr.callee else { return }; + + if memb_expr.is_computed() || memb_expr.optional() || call_expr.optional { + return; + } + + let (current, replacement, span) = match memb_expr.static_property_info() { + Some((span, "charCodeAt")) => ("charCodeAt", "codePointAt", span), + Some((span, "fromCharCode")) => ("fromCharCode", "fromCodePoint", span), + _ => return, + }; + + ctx.diagnostic(PreferCodePointDiagnostic(span, replacement, current)); + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + r#""๐Ÿฆ„".codePointAt(0)"#, + r#"foo.charCodeAt"#, + r#"new foo.charCodeAt"#, + r#"charCodeAt(0)"#, + r#"foo.charCodeAt?.(0)"#, + r#"foo?.charCodeAt(0)"#, + r#"foo[charCodeAt](0)"#, + r#"foo["charCodeAt"](0)"#, + r#"foo.notCharCodeAt(0)"#, + r#"String.fromCodePoint(0x1f984)"#, + r#"String.fromCodePoint"#, + r#"new String.fromCodePoint"#, + r#"fromCodePoint(foo)"#, + r#"String.fromCodePoint?.(foo)"#, + r#"String?.fromCodePoint(foo)"#, + r#"window.String.fromCodePoint(foo)"#, + r#"String[fromCodePoint](foo)"#, + r#"String["fromCodePoint"](foo)"#, + r#"String.notFromCodePoint(foo)"#, + r#"NotString.fromCodePoint(foo)"#, + ]; + + let fail = vec![ + r#"string.charCodeAt(index)"#, + r#"(( (( string )).charCodeAt( ((index)), )))"#, + r#"String.fromCharCode( code )"#, + r#"(( (( String )).fromCharCode( ((code)), ) ))"#, + ]; + + Tester::new_without_config(PreferCodePoint::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/prefer_code_point.snap b/crates/oxc_linter/src/snapshots/prefer_code_point.snap new file mode 100644 index 000000000..818502c94 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/prefer_code_point.snap @@ -0,0 +1,33 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: prefer_code_point +--- + โš  eslint-plugin-unicorn(prefer-code-point): Prefer `codePointAt` over `charCodeAt` + โ•ญโ”€[prefer_code_point.tsx:1:1] + 1 โ”‚ string.charCodeAt(index) + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Unicode is better supported in `codePointAt` than `charCodeAt` + + โš  eslint-plugin-unicorn(prefer-code-point): Prefer `codePointAt` over `charCodeAt` + โ•ญโ”€[prefer_code_point.tsx:1:1] + 1 โ”‚ (( (( string )).charCodeAt( ((index)), ))) + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Unicode is better supported in `codePointAt` than `charCodeAt` + + โš  eslint-plugin-unicorn(prefer-code-point): Prefer `fromCodePoint` over `fromCharCode` + โ•ญโ”€[prefer_code_point.tsx:1:1] + 1 โ”‚ String.fromCharCode( code ) + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Unicode is better supported in `fromCodePoint` than `fromCharCode` + + โš  eslint-plugin-unicorn(prefer-code-point): Prefer `fromCodePoint` over `fromCharCode` + โ•ญโ”€[prefer_code_point.tsx:1:1] + 1 โ”‚ (( (( String )).fromCharCode( ((code)), ) )) + ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ•ฐโ”€โ”€โ”€โ”€ + help: Unicode is better supported in `fromCodePoint` than `fromCharCode` + +