Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ mod react_perf {

mod unicorn {
pub mod catch_error_name;
pub mod consistent_date_clone;
pub mod consistent_empty_array_spread;
pub mod consistent_existence_index_check;
pub mod consistent_function_scoping;
Expand Down Expand Up @@ -934,6 +935,7 @@ oxc_macros::declare_all_lint_rules! {
typescript::prefer_ts_expect_error,
typescript::triple_slash_reference,
unicorn::catch_error_name,
unicorn::consistent_date_clone,
unicorn::consistent_empty_array_spread,
unicorn::consistent_existence_index_check,
unicorn::consistent_function_scoping,
Expand Down
134 changes: 134 additions & 0 deletions crates/oxc_linter/src/rules/unicorn/consistent_date_clone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use crate::{AstNode, context::LintContext, rule::Rule};
use oxc_ast::AstKind;
use oxc_ast::ast::{Argument, Expression};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};

fn consistent_date_clone_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Unnecessary `.getTime()` call")
.with_help("Prefer passing `Date` directly to the constructor when cloning")
.with_label(span)
}

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

declare_oxc_lint!(
/// ### What it does
///
/// The Date constructor can clone a `Date` object directly when passed as an argument,
/// making timestamp conversion unnecessary. This rule enforces the use of the
/// direct `Date` cloning instead of using `.getTime()` for conversion.
///
/// ### Why is this bad?
///
/// Using `.getTime()` to convert a `Date` object to a timestamp and then back to a
/// `Date` is redundant and unnecessary. Simply passing the `Date` object to the
/// `Date` constructor is cleaner and more efficient.
///
/// ### Examples
///
/// Examples of **incorrect** code for this rule:
/// ```js
/// new Date(date.getTime());
/// ```
///
/// Examples of **correct** code for this rule:
/// ```js
/// new Date(date);
/// ```
ConsistentDateClone,
unicorn,
style,
fix
);

impl Rule for ConsistentDateClone {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::NewExpression(expr) = &node.kind() {
if !(expr.callee.is_specific_id("Date")
&& expr.arguments.len() == 1
&& expr.type_parameters.is_none())
{
return;
}
if let Argument::CallExpression(expr) = &expr.arguments[0] {
if let Expression::StaticMemberExpression(callee) = &expr.callee {
if callee.property.name.as_str() == "getTime"
&& expr.arguments.len() == 0
&& !expr.optional
&& !callee.optional
{
ctx.diagnostic_with_fix(
consistent_date_clone_diagnostic(expr.span),
|fixer| {
fixer.delete_range(Span::new(
callee.object.span().end,
expr.span.end,
))
},
);
}
}
}
}
}
}

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

let pass = vec![
"new Date(date)",
"date.getTime()",
"new Date(...date.getTime())",
"new Date(getTime())",
"new Date(date.getTime(), extraArgument)",
"new Date(date.not_getTime())",
"new Date(date?.getTime())",
"new NotDate(date.getTime())",
"new Date(date[getTime]())",
"new Date(date.getTime(extraArgument))",
"Date(date.getTime())",
// TODO: We may support these cases in future
"new Date(
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds(),
);",
"new Date(
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
);",
];

let fail = vec![
"new Date(date.getTime())",
"new Date(date.getTime(),)",
"new Date((0, date).getTime())",
"new Date(date.getTime(/* comment */))",
"new Date(date./* comment */getTime())",
];

let fix = vec![
("new Date(date.getTime())", "new Date(date)"),
("new Date(date.getTime(),)", "new Date(date,)"),
("new Date((0, date).getTime())", "new Date((0, date))"),
("new Date(date.getTime(/* comment */))", "new Date(date)"),
("new Date(date./* comment */getTime())", "new Date(date)"),
];

Tester::new(ConsistentDateClone::NAME, ConsistentDateClone::PLUGIN, pass, fail)
.expect_fix(fix)
.test_and_snapshot();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
source: crates/oxc_linter/src/tester.rs
assertion_line: 357
---
⚠ eslint-plugin-unicorn(consistent-date-clone): Unnecessary `.getTime()` call
╭─[consistent_date_clone.tsx:1:10]
1 │ new Date(date.getTime())
· ──────────────
╰────
help: Prefer passing `Date` directly to the constructor when cloning

⚠ eslint-plugin-unicorn(consistent-date-clone): Unnecessary `.getTime()` call
╭─[consistent_date_clone.tsx:1:10]
1 │ new Date(date.getTime(),)
· ──────────────
╰────
help: Prefer passing `Date` directly to the constructor when cloning

⚠ eslint-plugin-unicorn(consistent-date-clone): Unnecessary `.getTime()` call
╭─[consistent_date_clone.tsx:1:10]
1 │ new Date((0, date).getTime())
· ───────────────────
╰────
help: Prefer passing `Date` directly to the constructor when cloning

⚠ eslint-plugin-unicorn(consistent-date-clone): Unnecessary `.getTime()` call
╭─[consistent_date_clone.tsx:1:10]
1 │ new Date(date.getTime(/* comment */))
· ───────────────────────────
╰────
help: Prefer passing `Date` directly to the constructor when cloning

⚠ eslint-plugin-unicorn(consistent-date-clone): Unnecessary `.getTime()` call
╭─[consistent_date_clone.tsx:1:10]
1 │ new Date(date./* comment */getTime())
· ───────────────────────────
╰────
help: Prefer passing `Date` directly to the constructor when cloning