Skip to content

Conversation

Showichiro
Copy link
Owner

@Showichiro Showichiro commented Feb 28, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a robust deep merge capability that combines object properties, handles nested structures, and concatenates arrays.
    • Added a type merging utility for seamlessly combining different type definitions.
  • Documentation

    • Updated with practical examples demonstrating the new deep merge and type merging functionality.
  • Chores

    • Upgraded the package version to 0.6.0.

Copy link
Contributor

coderabbitai bot commented Feb 28, 2025

Walkthrough

This pull request adds a new deep merge functionality through the $deepMerge function and the DeepMerge type. The README now provides examples demonstrating how to merge objects and types, including handling nested properties and arrays. The implementation in object.ts features recursive merging with input validation and error handling. Corresponding tests in object.test.ts and type.test.ts verify the functionality and type correctness. Additionally, the package version is updated in deno.json from 0.5.0 to 0.6.0.

Changes

Files Change Summary
README.md Added new examples demonstrating the $deepMerge utility and the DeepMerge type for merging objects and types.
deno.json Updated version number from "0.5.0" to "0.6.0".
object.ts, object.test.ts Introduced $deepMerge function with recursive merging logic, including concatenation of arrays, merging of nested properties, and error handling; added tests for various scenarios.
types.ts, type.test.ts Introduced new DeepMerge type for recursive type merging with comprehensive tests to ensure accuracy across different merging scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant DM as $deepMerge
    participant ML as Merge Logic

    U->>DM: Call $deepMerge(target, source)
    DM->>DM: Validate target and source objects
    DM->>ML: Recursively merge properties
    ML-->>DM: Return merged property values
    DM->>U: Return final merged object
Loading

Poem

Hop, skip, and code away,
I found a merge that saves the day,
Arrays and objects twined so neat,
Nested secrets now complete,
With every test my joy takes flight,
A rabbit’s cheer in every byte! 🥕

✨ Finishing Touches
  • 📝 Generate Docstrings

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@codecov-commenter
Copy link

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

Attention: Patch coverage is 92.85714% with 4 lines in your changes missing coverage. Please review.

Project coverage is 94.82%. Comparing base (89b528b) to head (41f33c7).

Files with missing lines Patch % Lines
object.ts 92.85% 4 Missing ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #19      +/-   ##
==========================================
- Coverage   95.17%   94.82%   -0.36%     
==========================================
  Files           3        3              
  Lines         311      367      +56     
  Branches       89      105      +16     
==========================================
+ Hits          296      348      +52     
- Misses         15       19       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

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 (8)
object.test.ts (1)

177-177: Consider adding English comments for better accessibility.

The Japanese comments describing test cases could be supplemented with English translations for broader accessibility to international contributors.

-  // 基本的なオブジェクトのマージ
+  // Basic object merging (基本的なオブジェクトのマージ)

Also applies to: 183-183, 189-189, 195-195, 204-204

README.md (1)

80-95: Consider adding clarification about array merging behavior.

The example shows that arrays are concatenated during merging, but this might not be immediately obvious to all users. Consider adding a brief explanation to clarify this specific behavior.

/* Output:
{
  a: 1,
  b: {
    c: "hello",
    d: [1, 2, 3, 4],  // Arrays are concatenated, not replaced
    e: true
  },
  f: "world"
}
*/
type.test.ts (1)

145-145: Consider adding English comments for better accessibility.

As with the object tests, consider supplementing the Japanese comments with English translations to improve accessibility for international contributors.

-  // 基本的なオブジェクトのマージ
+  // Basic object merging (基本的なオブジェクトのマージ)

Also applies to: 151-151, 161-161, 171-171, 177-177

object.ts (5)

79-85: Replace Japanese comment with English for consistency.

The validation code is correct, but there's a Japanese comment that should be translated to English to maintain consistency with the rest of the codebase.

-  // 基本的な検証
+  // Basic validation

87-91: Replace Japanese comment with English for consistency.

The merge function is well-defined with appropriate generic types, but there's another Japanese comment that should be translated to English.

-  // 2つのオブジェクトをマージする内部関数
+  // Internal function to merge two objects

96-129: Consider extracting the object check condition into a helper function.

The deep merging logic is sound, but the condition to check if values are objects (lines 103-110) is lengthy and repeated elsewhere. Consider extracting it into a helper function for better readability and maintainability.

+  // Helper function to check if a value is a non-null, non-undefined object
+  const isObject = (value: unknown): boolean => {
+    return value !== null && 
+           value !== undefined && 
+           typeof value === "object";
+  };

   // In the merge function:
   if (
-    targetValue !== null &&
-    targetValue !== undefined &&
-    sourceValue !== null &&
-    sourceValue !== undefined &&
-    typeof targetValue === "object" &&
-    typeof sourceValue === "object"
+    isObject(targetValue) && isObject(sourceValue)
   ) {
     // ... rest of the logic

75-135: The function doesn't support merging more than two objects.

The JSDoc mentions "Deeply merges two or more objects" but the implementation only accepts two objects. Consider extending the function to handle multiple objects using rest parameters.

-export const $deepMerge = <T extends object, U extends object>(
-  target: T,
-  source: U,
-): DeepMerge<T, U> => {
+export const $deepMerge = <T extends object, U extends object, V extends object[]>(
+  target: T,
+  source: U,
+  ...sources: V
+): DeepMerge<T, U> & DeepMerge<DeepMerge<T, U>, V[number]> => {
   // Basic validation
   if (typeof target !== "object" || target === null) {
     throw new Error("expected a non-null object for the first argument");
   }
   if (typeof source !== "object" || source === null) {
     throw new Error("expected a non-null object for the second argument");
   }
+  
+  // Validate additional sources
+  for (let i = 0; i < sources.length; i++) {
+    if (typeof sources[i] !== "object" || sources[i] === null) {
+      throw new Error(`expected a non-null object for argument ${i + 3}`);
+    }
+  }

   // Internal function to merge two objects
   const merge = <A extends object, B extends object>(
     target: A,
     source: B,
   ): DeepMerge<A, B> => {
     // Implementation remains the same
   };

-  return merge(target, source);
+  // Merge the first two objects
+  let result = merge(target, source);
+  
+  // Merge any additional objects
+  for (const src of sources) {
+    result = merge(result, src);
+  }
+  
+  return result as DeepMerge<T, U> & DeepMerge<DeepMerge<T, U>, V[number]>;

75-135: Add protection against circular references.

The recursive merging approach could potentially cause stack overflow errors if there are circular references in the objects being merged.

Consider adding a simple mechanism to detect circular references, such as using a WeakMap to track already processed objects:

 export const $deepMerge = <T extends object, U extends object>(
   target: T,
   source: U,
 ): DeepMerge<T, U> => {
   // Basic validation
   if (typeof target !== "object" || target === null) {
     throw new Error("expected a non-null object for the first argument");
   }
   if (typeof source !== "object" || source === null) {
     throw new Error("expected a non-null object for the second argument");
   }

+  // Track processed objects to avoid circular references
+  const processedObjects = new WeakMap();
+
   // Internal function to merge two objects
   const merge = <A extends object, B extends object>(
     target: A,
     source: B,
+    path = ""
   ): DeepMerge<A, B> => {
+    // Check for circular references
+    if (processedObjects.has(target) && processedObjects.get(target).has(source)) {
+      return processedObjects.get(target).get(source);
+    }
+
+    // Mark these objects as being processed
+    if (!processedObjects.has(target)) {
+      processedObjects.set(target, new WeakMap());
+    }
+    if (!processedObjects.get(target).has(source)) {
+      const result = { ...target } as Record<PropertyKey, unknown>;
+      processedObjects.get(target).set(source, result);
+      
+      // Rest of the merge logic
+      // ...
+      
+      return result as DeepMerge<A, B>;
+    }
+
     // Result object creation and merging logic
     // ...
   };

   return merge(target, source);
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89b528b and 41f33c7.

📒 Files selected for processing (6)
  • README.md (2 hunks)
  • deno.json (1 hunks)
  • object.test.ts (2 hunks)
  • object.ts (1 hunks)
  • type.test.ts (2 hunks)
  • types.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • deno.json
🔇 Additional comments (18)
object.test.ts (3)

3-3: Good addition of the $deepMerge import.

The import statement has been properly updated to include the new $deepMerge function alongside existing imports.


176-209: Well-structured test cases for $deepMerge.

The test cases comprehensively cover various scenarios:

  • Basic object merging
  • Nested object merging
  • Array merging
  • Complex nested structures
  • Arrays within nested objects

This ensures the functionality works correctly across different use cases.


211-240: Thorough error handling tests for $deepMerge.

The tests properly verify that the function rejects non-object arguments with appropriate error messages. This ensures robust input validation.

types.ts (2)

220-255: Well-implemented DeepMerge type with comprehensive JSDoc.

The type implementation is robust and handles various edge cases:

  • Recursive merging of nested objects
  • Special handling for arrays
  • Property override behavior

The JSDoc comments are excellent, providing clear explanation of purpose, behavior, and examples.


242-243:

❓ Verification inconclusive

Verify array merging behavior is consistent with runtime implementation.

The type system creates a union of array element types (Array<T1 | U1>), while the runtime implementation likely concatenates arrays. This could lead to confusion for users who expect the type to reflect the concatenated structure.


🏁 Script executed:

#!/bin/bash
# Description: Verify that the runtime implementation of $deepMerge for arrays is consistent with type definition.

# Find the implementation of $deepMerge in object.ts to see how arrays are handled
rg -A 20 -B 5 "function \$deepMerge" 

Length of output: 37


Action Required: Manually Verify the Runtime Array Merging Behavior for $deepMerge

The automated search for the implementation in object.ts did not return any output, so we couldn’t automatically confirm how arrays are handled at runtime. The type definition merges array element types into a union (Array<T1 | U1>), but it remains unclear whether the runtime function concatenates arrays or behaves differently.

  • Next Steps:
    • Manually inspect the implementation of $deepMerge (or its equivalent) to determine if array merging is done via concatenation.
    • Confirm that the type behavior (producing a union of element types) accurately reflects the actual runtime behavior.
    • If there is a divergence between the type definition and runtime behavior (e.g., if runtime concatenates arrays), consider updating the type annotations or documentation to prevent any user confusion.
README.md (2)

63-95: Clear and informative examples for $deepMerge function.

The examples clearly demonstrate how to use the $deepMerge function with:

  • Basic object merging
  • Nested property handling
  • Array merging behavior

The output comments provide helpful visualization of the expected results.


405-436: Good demonstration of the DeepMerge type utility.

The type examples effectively show how the DeepMerge type works with complex object structures and array types. The expected result is well-documented.

type.test.ts (3)

3-3: Good addition of DeepMerge to the imports.

The import statement has been properly updated to include the new DeepMerge type utility.


144-189: Comprehensive type tests for DeepMerge.

The type tests thoroughly verify that DeepMerge works correctly across different scenarios:

  • Basic object merging
  • Nested object merging
  • Array type merging
  • Primitive value overrides
  • Complex nested structures

Using AssertTrue<IsExact<...>> ensures strict type checking.


162-169:

✅ Verification successful

Verify type behavior with runtime array merging.

The test correctly expects array types to be merged as unions (Array<string | boolean>), but ensure this is consistent with how the runtime implementation handles arrays (likely through concatenation).


🏁 Script executed:

#!/bin/bash
# Description: Compare the array type behavior with runtime implementation.

# Look for test cases that specifically test array merging behavior
rg -A 10 "array" object.test.ts

Length of output: 2314


Array Merging Behavior Verified

The type tests correctly merge array types into unions (e.g. Array<string | boolean>), and the runtime tests for $deepMerge in object.test.ts confirm that arrays are merged via concatenation (e.g. [1,2,4,5]). This alignment between type-level expectations and runtime behavior is as intended. Please continue to ensure that any future changes to deep merging preserve this consistency.

object.ts (8)

63-63: Import is correctly positioned.

The import for the DeepMerge type is appropriately placed and will be used to define the return type of the new function.


65-74: JSDoc is well structured and informative.

The documentation clearly describes the function purpose, parameters, return type, and potential exceptions. This maintains consistency with the documentation style of other functions in the file.


75-78: Function signature correctly uses generics.

The generic type parameters T and U appropriately constrain both parameters to be objects while allowing flexibility in the specific object types that can be merged.


92-94: The spread operator creates a shallow copy of the target.

This is correct for initializing the result object. Creating a new object ensures that the original objects aren't modified.


111-127: The array merging strategy should be documented.

The function concatenates arrays rather than recursively merging their elements. This is a valid approach but should be explicitly mentioned in the JSDoc since users might expect different behavior.

The array handling logic looks correct, but consider adding a note to the JSDoc about how arrays are handled:

 * @returns {DeepMerge<T, U>} A new object with all properties deeply merged.
+* @remarks Arrays are concatenated rather than deeply merged.
 * @throws Will throw an error if any argument is not a non-null object.

103-110: Check object type after non-null and non-undefined check.

The current implementation checks typeof value === "object" after confirming the value is not null or undefined, which is good practice. This is the correct order because typeof null returns "object" in JavaScript.


131-132: Type casting is used appropriately.

The result is correctly cast to DeepMerge<A, B> which should ensure proper type information is preserved.


134-135: Function execution is straightforward.

The implementation correctly calls the inner merge function with the target and source objects.

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.

2 participants