Skip to content

fix: merge symbol keys instead of overwriting them#181

Open
MFA-G wants to merge 2 commits into
unjs:mainfrom
MFA-G:fix/merge-symbol-keys
Open

fix: merge symbol keys instead of overwriting them#181
MFA-G wants to merge 2 commits into
unjs:mainfrom
MFA-G:fix/merge-symbol-keys

Conversation

@MFA-G

@MFA-G MFA-G commented Jun 27, 2026

Copy link
Copy Markdown

Summary

Fixes #145.

_defu iterated over the base object with Object.keys(baseObject), which skips symbol keys. The result object is built from { ...defaults }, and spread does copy enumerable symbol keys. So symbol-keyed properties from later (lower-priority) arguments overwrote earlier (higher-priority) ones instead of being merged.

const [a, b, c] = [Symbol("a"), Symbol("b"), Symbol("c")];
defu({ [a]: "a", [c]: ["a", "b"] }, { [a]: "bbb", [b]: "c", [c]: ["c", "d"] });

// before: { [a]: "bbb", [b]: "c", [c]: ["c", "d"] }   ❌ symbol keys not merged
// after:  { [a]: "a",   [b]: "c", [c]: ["a","b","c","d"] }  ✅

Changes

  • Add getOwnEnumerableKeys to src/_utils.ts — returns own enumerable keys including symbols, mirroring object-spread semantics. Non-enumerable symbols stay excluded (consistent with how Object.keys excludes non-enumerable string keys and with the spread used to build the result).
  • Use it in _defu instead of Object.keys.

This keeps recursive merging, array concat, and the merger callback working for symbol keys too.

Tests

Added three regression tests to test/defu.test.ts:

  • merging symbol keys across arguments (the issue's reproduction)
  • recursive merge of nested symbol-keyed objects
  • non-enumerable symbol keys are ignored

All checks pass locally: pnpm lint, pnpm test:types, and pnpm vitest run (26 tests).

Summary by CodeRabbit

  • New Features

    • Default merging now includes enumerable symbol-keyed properties, aligning more closely with standard object spread behavior.
    • Added support for collecting all own enumerable keys, including symbols.
  • Bug Fixes

    • Improved merging for nested objects and arrays when symbol keys are present.
    • Non-enumerable symbol properties remain excluded from merged results.
  • Tests

    • Added coverage for symbol-key merging scenarios, including custom merge behavior.

`_defu` iterated only `Object.keys(baseObject)`, which skips symbol keys,
while `{ ...defaults }` copies them via spread. As a result symbol-keyed
properties from later (lower-priority) arguments overwrote earlier ones
instead of being merged.

Add a `getOwnEnumerableKeys` helper that returns own enumerable keys
including symbols (mirroring object-spread semantics) and use it when
iterating the base object. Non-enumerable symbols stay excluded.

Fixes unjs#145
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4155fbf-c217-4396-a764-1803ec784cf3

📥 Commits

Reviewing files that changed from the base of the PR and between 266209f and ca3a6b1.

📒 Files selected for processing (1)
  • test/defu.test.ts

📝 Walkthrough

Walkthrough

defu now enumerates own enumerable symbol keys during merging via a new helper. Tests cover enumerable symbol keys, nested symbol-keyed objects, custom symbol-key merging, and non-enumerable symbol properties.

Changes

Symbol-key merge support

Layer / File(s) Summary
Enumerable key helper
src/_utils.ts
A helper collects own enumerable string keys and own enumerable symbol keys.
Merge loop uses helper
src/defu.ts
defu imports the helper and iterates over enumerable own keys while preserving the existing merge flow.
Symbol-key tests
test/defu.test.ts
Tests cover enumerable symbol merging, nested symbol-keyed objects, custom merge logic, and ignored non-enumerable symbol properties.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 I hopped through keys both bright and sly,
Symbols now merge as they float by.
Enumerable paws, in a tidy line,
Make defu's bundles match just fine.

🚥 Pre-merge checks | ✅ 5
✅ 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 concisely and accurately describes the main change: merging symbol keys instead of overwriting them.
Linked Issues check ✅ Passed The changes address #145 by iterating enumerable symbol keys during merging and add tests for the reported symbol-key cases.
Out of Scope Changes check ✅ Passed The helper extraction and symbol-key tests are directly tied to the reported bug and do not introduce unrelated functionality.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/defu.test.ts (1)

138-165: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a symbol-keyed custom merger regression.

These tests cover recursion and array concatenation, but not the merger(object, key, value, namespace) branch in src/defu.ts Line 26. Since symbol enumeration now changes that path too, a small callback-specific test would lock down the remaining contract from issue #145.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/defu.test.ts` around lines 138 - 165, Add a regression test for the
symbol-keyed custom merger path in defu, since the current symbol tests only
cover default recursion and array merging. Extend test/defu.test.ts with a case
that uses the defu custom merger callback path from src/defu.ts (the
merger(object, key, value, namespace) branch) and verifies symbol keys are still
handled correctly when symbols are enumerated. Keep the test focused on the
existing defu symbol behavior and ensure it guards the callback-specific
contract from issue `#145`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@test/defu.test.ts`:
- Around line 138-165: Add a regression test for the symbol-keyed custom merger
path in defu, since the current symbol tests only cover default recursion and
array merging. Extend test/defu.test.ts with a case that uses the defu custom
merger callback path from src/defu.ts (the merger(object, key, value, namespace)
branch) and verifies symbol keys are still handled correctly when symbols are
enumerated. Keep the test focused on the existing defu symbol behavior and
ensure it guards the callback-specific contract from issue `#145`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 43ba4f10-a10c-4947-9250-b649e74f9e9e

📥 Commits

Reviewing files that changed from the base of the PR and between 82632b6 and 266209f.

📒 Files selected for processing (3)
  • src/_utils.ts
  • src/defu.ts
  • test/defu.test.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Symbol keys are not merged

1 participant