Skip to content

NaN Coercion#603

Merged
yamcodes merged 6 commits intocoercionfrom
599-nan-coercion
Dec 22, 2025
Merged

NaN Coercion#603
yamcodes merged 6 commits intocoercionfrom
599-nan-coercion

Conversation

@yamcodes
Copy link
Owner

@yamcodes yamcodes commented Dec 22, 2025

Closes #599

Summary by CodeRabbit

  • New Features

    • Number coercion now converts the string "NaN" to the JavaScript NaN value when the schema explicitly supports it.
    • NaN values propagate through numeric coercion instead of being filtered out.
  • Tests

    • Added test cases validating NaN coercion behavior and confirming NaN rejection in standard number contexts.

✏️ Tip: You can customize this high-level summary in your review settings.

- Coerce 'NaN' string to Number.NaN for numeric types.
- Standard 'number' keyword rejects coerced NaN.
- Added tests for 'NaN' string coercion.
- Updated coercion logic to include NaN.
- Added "NaN" support for number coercion
- Improved central coercion logic
- Updated tests for new coercion behavior
@changeset-bot
Copy link

changeset-bot bot commented Dec 22, 2025

⚠️ No Changeset found

Latest commit: 7d1b3d7

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes changesets to release 3 packages
Name Type
@repo/keywords Minor
@repo/scope Patch
@repo/types Patch

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Dec 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
arkenv Ready Ready Preview, Comment Dec 22, 2025 10:21pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 22, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The changes add support for coercing the string "NaN" to the JavaScript NaN value. NaN guards are removed from coercion logic to allow NaN propagation, while a specific check in the number parsing morph detects the string "NaN" and returns Number.NaN. Tests validate both successful coercion and rejection by non-NaN numeric types.

Changes

Cohort / File(s) Change Summary
Changelog
.changeset/humble-lizards-judge.md
Updated changelog entry to document added NaN-specific support in the coercion system.
Core NaN Coercion Logic
packages/internal/keywords/src/index.ts
Added detection in maybeParsedNumber morph: strings matching "NaN" (after trim) now return Number.NaN instead of attempting numeric conversion.
Coercion Guard Removal
packages/arkenv/src/utils/coerce.ts
Removed NaN guards in four coercion paths (root data, array items, object properties, record properties) to permit NaN values to propagate rather than fall back to boolean coercion.
NaN Coercion Tests
packages/arkenv/src/utils/coerce.test.ts
Added two test cases: validation that "NaN" string coerces to NaN with number.NaN schema, and confirmation that "NaN" is rejected by plain number type.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Poem

🐰 A string "NaN" hops through the coercer now,
No longer stopped by guards, it takes a bow,
Into NaN it transforms—a number too strange,
The morph detects it, no longer a range,
Not-a-number becomes not-a-string—hooray! 🎉

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'NaN Coercion' clearly summarizes the main change in the PR, which adds support for coercing the string 'NaN' to JavaScript Number.NaN.
Linked Issues check ✅ Passed The PR implements the objective from issue #599 by adding NaN coercion support: the string 'NaN' is now recognized and coerced to Number.NaN for numeric types, with corresponding tests added.
Out of Scope Changes check ✅ Passed All changes are directly related to the NaN coercion objective: keyword logic updates, coercion logic refinement, and test coverage additions for NaN handling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added docs Improvements or additions to documentation arkenv Changes to the `arkenv` npm package. tests This issue or PR is about adding, removing or changing tests labels Dec 22, 2025
@yamcodes
Copy link
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 22, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/internal/keywords/src/index.ts (1)

12-18: Optimize redundant string trimming.

The string is trimmed twice: once at line 14 and again at line 15. Consider storing the trimmed value to avoid redundant operations.

🔎 Proposed optimization
 export const maybeParsedNumber = type("unknown").pipe((s) => {
 	if (typeof s === "number") return s;
-	if (typeof s !== "string" || s.trim() === "") return s;
-	if (s.trim() === "NaN") return Number.NaN;
-	const n = Number(s);
+	if (typeof s !== "string") return s;
+	const trimmed = s.trim();
+	if (trimmed === "") return s;
+	if (trimmed === "NaN") return Number.NaN;
+	const n = Number(trimmed);
 	return Number.isNaN(n) ? s : n;
 });

Verify case-sensitivity requirements for "NaN" string.

The implementation only recognizes the exact string "NaN" (case-sensitive). While this matches JavaScript conventions, consider whether case-insensitive matching (e.g., "nan", "NAN") should be supported for better user experience.

packages/arkenv/src/utils/coerce.ts (1)

174-182: Update comment to reflect NaN handling change.

The logic correctly propagates NaN values, but the comment at line 176 is now outdated. It states "is NaN/invalid, try boolean", but NaN is now accepted as a valid number type.

🔎 Suggested comment update
 				} else {
 					const asNumber = maybeParsedNumber(original);
-					// If numeric parsing didn't change type (still string) or is NaN/invalid, try boolean
+					// If numeric parsing didn't produce a number, try boolean coercion
 					if (typeof asNumber === "number") {
 						record[lastKey] = asNumber;
 					} else {
 						record[lastKey] = maybeParsedBoolean(original);
 					}
 				}
packages/arkenv/src/utils/coerce.test.ts (1)

195-203: Consider type narrowing instead of @ts-expect-error.

The test correctly verifies NaN coercion, but the @ts-expect-error directive suppresses a legitimate type safety concern. Consider adding explicit type narrowing for better type safety.

🔎 Suggested type narrowing approach
 	it("should coerce 'NaN' string to NaN number", () => {
 		const schema = type({
 			VAL: "number.NaN",
 		});
 		const coercedSchema = coerce(schema);
 		const result = coercedSchema({ VAL: "NaN" });
-		// @ts-expect-error
+		expect(result).not.toBeInstanceOf(ArkErrors);
+		if (result instanceof ArkErrors) return;
 		expect(result.VAL).toBeNaN();
 	});
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 167ba08 and cca4aff.

📒 Files selected for processing (4)
  • .changeset/humble-lizards-judge.md
  • packages/arkenv/src/utils/coerce.test.ts
  • packages/arkenv/src/utils/coerce.ts
  • packages/internal/keywords/src/index.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/coding-guidelines.mdc)

**/*.{ts,tsx}: Prefer type over interface for type definitions in TypeScript
Use TypeScript 5.1+ features when appropriate
Leverage const type parameters for better inference in TypeScript
Use JSDoc comments for public APIs
Use tabs for indentation (configured in Biome)
Use double quotes for strings (configured in Biome)
Organize imports automatically (Biome handles this)
Avoid explicit types when TypeScript can infer them (noInferrableTypes error)
Use as const where appropriate for immutable values (useAsConstAssertion error)
Don't reassign function parameters (noParameterAssign error)
Place default parameters last in function signatures (useDefaultParameterLast error)
Always initialize enum values (useEnumInitializers error)
Declare one variable per statement (useSingleVarDeclarator error)
Avoid unnecessary template literals (noUnusedTemplateLiteral error)
Prefer Number.parseInt over global parseInt (useNumberNamespace error)
Use kebab-case for TypeScript filenames (e.g., create-env.ts)
Use camelCase for function names (e.g., createEnv)
Use PascalCase for type names (e.g., ArkEnvError)
Use UPPER_SNAKE_CASE for environment variables and constants
Include examples in JSDoc comments when helpful for public APIs
Document complex type logic with JSDoc comments
Use ArkEnvError for environment variable validation errors
Provide clear, actionable error messages that include the variable name and expected type

**/*.{ts,tsx}: Use createEnv(schema) as the main function for validated environment objects, available as the default export
Use built-in validators (host, port, url, email) from src/types.ts when available instead of custom validation
Use ArkEnvError for environment variable errors, not generic errors
Environment schema definitions should use built-in validators, ArkType string literals, and support default values in the schema pattern
Use logical grouping for related environment variables in schemas
Use descriptive env...

Files:

  • packages/internal/keywords/src/index.ts
  • packages/arkenv/src/utils/coerce.ts
  • packages/arkenv/src/utils/coerce.test.ts
**/index.ts

📄 CodeRabbit inference engine (.cursor/rules/coding-guidelines.mdc)

Use barrel exports (index.ts) for package entry points

Files:

  • packages/internal/keywords/src/index.ts
**/*.{ts,tsx,json,md}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Biome for linting and formatting instead of ESLint and Prettier

Files:

  • packages/internal/keywords/src/index.ts
  • packages/arkenv/src/utils/coerce.ts
  • packages/arkenv/src/utils/coerce.test.ts
packages/arkenv/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/arktype.mdc)

packages/arkenv/**/*.ts: Use ArkType's type() function to define schemas in environment variable definitions
Leverage ArkType's type inference for TypeScript types instead of manual type definitions
Use the scoped $ type system for custom types defined in scope.ts
Keep environment variable schemas readable and TypeScript-like using ArkType syntax
Use union types for enums in ArkType schemas (e.g., "'dev' | 'prod'") instead of separate enum definitions
Leverage ArkType's built-in types (e.g., string.host, number.port) where possible in environment schemas
Convert ArkType validation errors to ArkEnvError for user-friendly error messages that include variable name and expected type

Files:

  • packages/arkenv/src/utils/coerce.ts
  • packages/arkenv/src/utils/coerce.test.ts
packages/arkenv/src/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Main library implementation should be in src/create-env.ts, built-in validators in src/types.ts, error handling in src/errors.ts, and utilities in src/utils.ts

Files:

  • packages/arkenv/src/utils/coerce.ts
  • packages/arkenv/src/utils/coerce.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/coding-guidelines.mdc)

Co-locate tests with components: Component.tsx next to Component.test.tsx

**/*.test.{ts,tsx}: Test individual functions, components, and hooks in isolation with mocked dependencies using the *.test.ts or *.test.tsx suffix
Mock process.env in tests for different scenarios and save/restore original env in beforeEach/afterEach hooks
Use Vitest's describe/it structure in test files
Test edge cases including invalid and missing environment variable values
Use Vitest for testing framework

**/*.test.{ts,tsx}: Use Vitest for unit and integration tests
Test individual functions, components, and hooks in isolation with mocked dependencies in unit tests
Unit tests should focus on individual function logic and edge cases, component rendering and props, error handling and validation, and type checking
Unit tests should execute in less than 100ms per test
Mock external dependencies (clipboard, network, etc.) in unit tests
Co-locate unit test files with source files using naming convention: source file → test file (e.g., create-env.ts → create-env.test.ts)
Test component behavior, not aesthetics, and focus on what users can do and what the component guarantees through its API
Test component public API (props, events, and component contract), user behavior (clicks, typing, focus, keyboard, ARIA), state transitions, accessibility, and side effects in component tests
Do not test pure styling or CSS classes, library internals (Radix/shadcn), implementation details (hooks, setState, private variables), or visual variants in component tests
Use Testing Library with user-event for real user simulation in component tests
Query by role, name, label, and text (accessibility first) in component tests
Use beforeEach/afterEach for cleanup, not beforeAll/afterAll when possible
Keep tests fast, deterministic, and parallelizable
Mock at component boundaries (network, time, context)

Files:

  • packages/arkenv/src/utils/coerce.test.ts
🧠 Learnings (12)
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Leverage ArkType's built-in types (e.g., `string.host`, `number.port`) where possible in environment schemas

Applied to files:

  • .changeset/humble-lizards-judge.md
  • packages/arkenv/src/utils/coerce.ts
  • packages/arkenv/src/utils/coerce.test.ts
📚 Learning: 2025-09-10T19:35:18.179Z
Learnt from: yamcodes
Repo: yamcodes/arkenv PR: 136
File: .changeset/vast-bananas-win.md:2-3
Timestamp: 2025-09-10T19:35:18.179Z
Learning: The arkenv package is currently in v0.x.x (pre-1.0) development phase, where breaking changes are acceptable in minor version bumps according to semantic versioning conventions.

Applied to files:

  • .changeset/humble-lizards-judge.md
📚 Learning: 2025-12-22T19:44:07.593Z
Learnt from: yamcodes
Repo: yamcodes/arkenv PR: 596
File: examples/basic/index.ts:4-5
Timestamp: 2025-12-22T19:44:07.593Z
Learning: In examples/basic/index.ts: Use explicit ArkType syntax (e.g., "string.ip | 'localhost'", "0 <= number.integer <= 65535") instead of built-in validators (string.host, number.port) to showcase ArkType's type system capabilities for educational purposes.

Applied to files:

  • .changeset/humble-lizards-judge.md
  • packages/arkenv/src/utils/coerce.test.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Use union types for enums in ArkType schemas (e.g., `"'dev' | 'prod'"`) instead of separate enum definitions

Applied to files:

  • .changeset/humble-lizards-judge.md
  • packages/arkenv/src/utils/coerce.ts
  • packages/arkenv/src/utils/coerce.test.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Keep environment variable schemas readable and TypeScript-like using ArkType syntax

Applied to files:

  • .changeset/humble-lizards-judge.md
  • packages/arkenv/src/utils/coerce.ts
  • packages/arkenv/src/utils/coerce.test.ts
📚 Learning: 2025-11-24T16:04:00.957Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/coding-guidelines.mdc:0-0
Timestamp: 2025-11-24T16:04:00.957Z
Learning: Applies to **/*.{ts,tsx} : Prefer `Number.parseInt` over global `parseInt` (`useNumberNamespace` error)

Applied to files:

  • packages/internal/keywords/src/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Leverage ArkType's type inference for TypeScript types instead of manual type definitions

Applied to files:

  • packages/arkenv/src/utils/coerce.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Convert ArkType validation errors to `ArkEnvError` for user-friendly error messages that include variable name and expected type

Applied to files:

  • packages/arkenv/src/utils/coerce.ts
  • packages/arkenv/src/utils/coerce.test.ts
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.{ts,tsx} : Environment schema definitions should use built-in validators, ArkType string literals, and support default values in the schema pattern

Applied to files:

  • packages/arkenv/src/utils/coerce.test.ts
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.test.{ts,tsx} : Test edge cases including invalid and missing environment variable values

Applied to files:

  • packages/arkenv/src/utils/coerce.test.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Use ArkType's `type()` function to define schemas in environment variable definitions

Applied to files:

  • packages/arkenv/src/utils/coerce.test.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: ArkType validates environment variables at runtime and TypeScript types are inferred from the schema definition

Applied to files:

  • packages/arkenv/src/utils/coerce.test.ts
🧬 Code graph analysis (1)
packages/arkenv/src/utils/coerce.test.ts (2)
packages/arkenv/src/type.ts (1)
  • type (3-3)
packages/arkenv/src/utils/coerce.ts (1)
  • coerce (215-238)
🔇 Additional comments (5)
.changeset/humble-lizards-judge.md (1)

9-9: LGTM!

The changelog entry accurately documents the NaN support addition.

packages/arkenv/src/utils/coerce.ts (3)

115-121: LGTM - NaN propagation enabled for root data coercion.

The removal of the NaN guard correctly allows NaN values to propagate as valid numbers. Schema validation will reject NaN where appropriate (e.g., plain number keyword).


144-156: LGTM - NaN propagation enabled for array item coercion.

Consistent with the root data coercion approach, NaN values are now properly propagated for array items.


164-173: LGTM - NaN propagation enabled for object property arrays.

The change maintains consistency across all coercion paths.

packages/arkenv/src/utils/coerce.test.ts (1)

205-214: LGTM - Excellent test coverage for NaN rejection.

This test correctly verifies that the plain number keyword rejects NaN values even after coercion, which is the expected behavior. The error message validation ensures proper user feedback.

@yamcodes yamcodes merged commit 1483d57 into coercion Dec 22, 2025
4 of 5 checks passed
@yamcodes yamcodes deleted the 599-nan-coercion branch December 22, 2025 22:18
@yamcodes yamcodes mentioned this pull request Dec 22, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jan 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arkenv Changes to the `arkenv` npm package. docs Improvements or additions to documentation tests This issue or PR is about adding, removing or changing tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant