-
-
Notifications
You must be signed in to change notification settings - Fork 368
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter) eslint plugin unicorn: no zero fractions
- Loading branch information
Showing
3 changed files
with
312 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 137 additions & 0 deletions
137
crates/oxc_linter/src/rules/unicorn/no_zero_fractions.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
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)] | ||
enum NoZeroFractionsDiagnostic { | ||
#[error("eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number.")] | ||
#[diagnostic(severity(warning), help("Replace the number literal with `{1}`"))] | ||
ZeroFraction(#[label] Span, String), | ||
#[error("eslint-plugin-unicorn(no-zero-fractions): Don't use a dangling dot in the number.")] | ||
#[diagnostic(severity(warning), help("Replace the number literal with `{1}`"))] | ||
DanglingDot(#[label] Span, String), | ||
} | ||
|
||
#[derive(Debug, Default, Clone)] | ||
pub struct NoZeroFractions; | ||
|
||
declare_oxc_lint!( | ||
/// ### What it does | ||
/// | ||
/// Prevents the use of zero fractions. | ||
/// | ||
/// ### Why is this bad? | ||
/// | ||
/// There is no difference in JavaScript between, for example, `1`, `1.0` and `1.`, so prefer the former for consistency and brevity. | ||
/// | ||
/// ### Example | ||
/// ```javascript | ||
/// // Bad | ||
/// const foo = 1.0; | ||
/// const foo = -1.0; | ||
/// const foo = 123_456.000_000; | ||
/// | ||
/// // Good | ||
/// const foo = 1; | ||
/// const foo = -1; | ||
/// const foo = 123456; | ||
/// const foo = 1.1; | ||
/// ``` | ||
NoZeroFractions, | ||
style | ||
); | ||
|
||
impl Rule for NoZeroFractions { | ||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { | ||
let AstKind::NumberLiteral(number_literal) = node.kind() else { | ||
return; | ||
}; | ||
|
||
let Some((fmt, is_dangling_dot)) = format_raw(number_literal.raw) else { return }; | ||
if fmt == number_literal.raw { | ||
return; | ||
}; | ||
|
||
ctx.diagnostic_with_fix( | ||
if is_dangling_dot { | ||
NoZeroFractionsDiagnostic::DanglingDot(number_literal.span, fmt.clone()) | ||
} else { | ||
NoZeroFractionsDiagnostic::ZeroFraction(number_literal.span, fmt.clone()) | ||
}, | ||
|| Fix::new(fmt, number_literal.span), | ||
); | ||
} | ||
} | ||
|
||
fn format_raw(raw: &str) -> Option<(String, bool)> { | ||
let (before, after_and_dot) = raw.split_once('.')?; | ||
let mut after_parts = after_and_dot.splitn(2, |c: char| !c.is_ascii_digit() && c != '_'); | ||
let dot_and_fractions = after_parts.next()?; | ||
let after = after_parts.next().unwrap_or(""); | ||
|
||
let fixed_dot_and_fractions = | ||
dot_and_fractions.trim_end_matches(|c: char| c == '0' || c == '.' || c == '_'); | ||
let formatted = format!( | ||
"{}{}{}{}", | ||
if before.is_empty() && fixed_dot_and_fractions.is_empty() { "0" } else { before }, | ||
if fixed_dot_and_fractions.is_empty() { "" } else { "." }, | ||
fixed_dot_and_fractions, | ||
after | ||
); | ||
|
||
Some((formatted, dot_and_fractions.is_empty())) | ||
} | ||
|
||
#[test] | ||
fn test() { | ||
use crate::tester::Tester; | ||
|
||
let pass = vec![ | ||
r#"const foo = "123.1000""#, | ||
r#"foo("123.1000")"#, | ||
r"const foo = 1", | ||
r"const foo = 1 + 2", | ||
r"const foo = -1", | ||
r"const foo = 123123123", | ||
r"const foo = 1.1", | ||
r"const foo = -1.1", | ||
r"const foo = 123123123.4", | ||
r"const foo = 1e3", | ||
r"1 .toString()", | ||
]; | ||
|
||
let fail = vec![ | ||
r"const foo = 1.0", | ||
r"const foo = 1.0 + 1", | ||
r"foo(1.0 + 1)", | ||
r"const foo = 1.00", | ||
r"const foo = 1.00000", | ||
r"const foo = -1.0", | ||
r"const foo = 123123123.0", | ||
r"const foo = 123.11100000000", | ||
r"const foo = 1.", | ||
r"const foo = +1.", | ||
r"const foo = -1.", | ||
r"const foo = 1.e10", | ||
r"const foo = +1.e-10", | ||
r"const foo = -1.e+10", | ||
r"const foo = (1.).toString()", | ||
r"1.00.toFixed(2)", | ||
r"1.00 .toFixed(2)", | ||
r"(1.00).toFixed(2)", | ||
r"1.00?.toFixed(2)", | ||
r"a = .0;", | ||
r"a = .0.toString()", | ||
r"function foo(){return.0}", | ||
r"function foo(){return.0.toString()}", | ||
r"function foo(){return.0+.1}", | ||
]; | ||
|
||
Tester::new_without_config(NoZeroFractions::NAME, pass, fail).test_and_snapshot(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
--- | ||
source: crates/oxc_linter/src/tester.rs | ||
expression: no_zero_fractions | ||
--- | ||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = 1.0 | ||
· ─── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = 1.0 + 1 | ||
· ─── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ foo(1.0 + 1) | ||
· ─── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = 1.00 | ||
· ──── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = 1.00000 | ||
· ─────── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = -1.0 | ||
· ─── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = 123123123.0 | ||
· ─────────── | ||
╰──── | ||
help: Replace the number literal with `123123123` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = 123.11100000000 | ||
· ─────────────── | ||
╰──── | ||
help: Replace the number literal with `123.111` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a dangling dot in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = 1. | ||
· ── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a dangling dot in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = +1. | ||
· ── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a dangling dot in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = -1. | ||
· ── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a dangling dot in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = 1.e10 | ||
· ───── | ||
╰──── | ||
help: Replace the number literal with `110` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a dangling dot in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = +1.e-10 | ||
· ────── | ||
╰──── | ||
help: Replace the number literal with `1-10` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a dangling dot in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = -1.e+10 | ||
· ────── | ||
╰──── | ||
help: Replace the number literal with `1+10` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a dangling dot in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ const foo = (1.).toString() | ||
· ── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ 1.00.toFixed(2) | ||
· ──── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ 1.00 .toFixed(2) | ||
· ──── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ (1.00).toFixed(2) | ||
· ──── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ 1.00?.toFixed(2) | ||
· ──── | ||
╰──── | ||
help: Replace the number literal with `1` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ a = .0; | ||
· ── | ||
╰──── | ||
help: Replace the number literal with `0` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ a = .0.toString() | ||
· ── | ||
╰──── | ||
help: Replace the number literal with `0` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ function foo(){return.0} | ||
· ── | ||
╰──── | ||
help: Replace the number literal with `0` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ function foo(){return.0.toString()} | ||
· ── | ||
╰──── | ||
help: Replace the number literal with `0` | ||
|
||
⚠ eslint-plugin-unicorn(no-zero-fractions): Don't use a zero fraction in the number. | ||
╭─[no_zero_fractions.tsx:1:1] | ||
1 │ function foo(){return.0+.1} | ||
· ── | ||
╰──── | ||
help: Replace the number literal with `0` | ||
|
||
|