Skip to content

Commit 3298c9e

Browse files
committed
fix(purity): report Date constructor arguments
1 parent b6f6bf6 commit 3298c9e

4 files changed

Lines changed: 53 additions & 48 deletions

File tree

plugins/eslint-plugin-react-x/src/rules/purity/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to the `react-x/purity` rule will be documented in this file
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
10+
### Fixed
11+
12+
- Treat `new Date(...)` calls with arguments as impure during render to match the React Compiler spec. Closes #1759.
13+
814
## [5.5.3-beta.1] - 2026-04-27
915

1016
### Added

plugins/eslint-plugin-react-x/src/rules/purity/purity.spec.diff.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ Detection uses only `IMPURE_FUNCS` and `IMPURE_CTORS`. Although `lib.ts` also ex
4040
- **ESLint**: only names present in `IMPURE_FUNCS` or `IMPURE_CTORS` are candidates.
4141
- **ESLint aliases**: `resolveBuiltinObjectName` follows simple variable-initializer chains to a global root, such as `const M = Math` and `const D = Date`.
4242
- **ESLint shadowing**: parameters, imports, function declarations, and other local definitions do not resolve as built-ins; implicit or unresolved globals do.
43-
- **ESLint constructors**: constructors in `IMPURE_CTORS` are reported, except `new Date(arg)` is allowed when at least one argument is present; zero-argument `new Date()` is reported.
43+
- **ESLint constructors**: constructors in `IMPURE_CTORS` are reported, including `new Date(...)` with or without arguments.
4444

45-
**Verdict**: Alias, shadowing, and the `new Date(arg)` exception are explicit ESLint behaviors. The cited React fixture does not establish corresponding upstream behavior.
45+
**Verdict**: Alias and shadowing are explicit ESLint behaviors. `new Date(...)` constructor calls are reported like the other constructors in `IMPURE_CTORS`.
4646

4747
## 3. Reporting
4848

@@ -69,7 +69,7 @@ There is no current upstream dual-location diagnostic to reproduce. The React ex
6969

7070
- React's active branch, option gate, signature check, `Purity` category, reason, and single call location.
7171
- The standalone React validator is present but not connected to `Pipeline.ts`.
72-
- ESLint's exclusive use of `IMPURE_FUNCS` / `IMPURE_CTORS`, component-hook ownership check, alias resolution, shadowing handling, and `new Date(arg)` exception.
72+
- ESLint's exclusive use of `IMPURE_FUNCS` / `IMPURE_CTORS`, component-hook ownership check, alias resolution, shadowing handling, and Date constructor handling.
7373

7474
### Fixture-verified
7575

plugins/eslint-plugin-react-x/src/rules/purity/purity.spec.ts

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,41 @@ ruleTester.run(RULE_NAME, rule, {
301301
`,
302302
errors: [{ messageId: "default" }],
303303
},
304+
{
305+
code: tsx`
306+
function Component() {
307+
const date = new Date("2024-01-01");
308+
return <div>{date.toISOString()}</div>;
309+
}
310+
`,
311+
errors: [{ messageId: "default" }],
312+
},
313+
{
314+
code: tsx`
315+
function Component({ timestamp }: { timestamp: number }) {
316+
const date = new Date(timestamp);
317+
return <div>{date.toISOString()}</div>;
318+
}
319+
`,
320+
errors: [{ messageId: "default" }],
321+
},
322+
{
323+
code: tsx`
324+
function Component({ year, month, day }: { year: number; month: number; day: number }) {
325+
const date = new Date(year, month, day);
326+
return <div>{date.toISOString()}</div>;
327+
}
328+
`,
329+
errors: [{ messageId: "default" }],
330+
},
331+
{
332+
code: tsx`
333+
function Component({ myDate }: { myDate: string }) {
334+
return new Date(myDate) < new Date("2020-01-01") ? <span>one</span> : <span>two</span>;
335+
}
336+
`,
337+
errors: [{ messageId: "default" }, { messageId: "default" }],
338+
},
304339
{
305340
code: tsx`
306341
function Component() {
@@ -528,6 +563,15 @@ ruleTester.run(RULE_NAME, rule, {
528563
`,
529564
errors: [{ messageId: "default" }],
530565
},
566+
{
567+
code: tsx`
568+
function useFormattedDate(input: string) {
569+
const date = new Date(input);
570+
return date.toLocaleDateString();
571+
}
572+
`,
573+
errors: [{ messageId: "default" }],
574+
},
531575
{
532576
code: tsx`
533577
function useStorage() {
@@ -1423,48 +1467,6 @@ ruleTester.run(RULE_NAME, rule, {
14231467
`,
14241468
},
14251469
// -------------------------------------------------------------------------
1426-
// new Date(arg) with arguments is pure (deterministic)
1427-
// -------------------------------------------------------------------------
1428-
{
1429-
code: tsx`
1430-
function Component({ myDate }: { myDate: string }) {
1431-
return new Date(myDate) < new Date("2020-01-01") ? <span>one</span> : <span>two</span>;
1432-
}
1433-
`,
1434-
},
1435-
{
1436-
code: tsx`
1437-
function Component() {
1438-
const date = new Date("2024-01-01");
1439-
return <div>{date.toISOString()}</div>;
1440-
}
1441-
`,
1442-
},
1443-
{
1444-
code: tsx`
1445-
function Component({ timestamp }: { timestamp: number }) {
1446-
const date = new Date(timestamp);
1447-
return <div>{date.toISOString()}</div>;
1448-
}
1449-
`,
1450-
},
1451-
{
1452-
code: tsx`
1453-
function useFormattedDate(input: string) {
1454-
const date = new Date(input);
1455-
return date.toLocaleDateString();
1456-
}
1457-
`,
1458-
},
1459-
{
1460-
code: tsx`
1461-
function Component({ year, month, day }: { year: number; month: number; day: number }) {
1462-
const date = new Date(year, month, day);
1463-
return <div>{date.toISOString()}</div>;
1464-
}
1465-
`,
1466-
},
1467-
// -------------------------------------------------------------------------
14681470
// Local shadowing of builtins (should NOT be flagged)
14691471
// -------------------------------------------------------------------------
14701472
{

plugins/eslint-plugin-react-x/src/rules/purity/purity.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,6 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
7777
const builtinName = resolveBuiltinObjectName(context, expr);
7878
if (builtinName == null) return;
7979
if (!IMPURE_CTORS.has(builtinName)) return;
80-
// `new Date(arg)` with arguments is pure (deterministic),
81-
// only `new Date()` without arguments is impure (depends on current time).
82-
if (builtinName === "Date" && node.arguments.length > 0) return;
8380
const func = Traverse.findParent(node, Check.isFunction);
8481
if (func == null) return;
8582
nEntries.push({ func, node });

0 commit comments

Comments
 (0)