Closed cheton closed 3 days ago
Open the branch in Web Editor • VS Code • Insiders
Open Preview
Latest commit: 90c3790beb680dde97b03855e7724e1cd5f6f986
The changes in this PR will be included in the next version bump.
Not sure what this means? Click here to learn what changesets are.
Click here if you're a maintainer who wants to add another changeset to this PR
Here are some key observations to aid the review process:
โฑ๏ธ Estimated effort to review: 3 ๐ต๐ต๐ตโชโช |
๐งช PR contains tests |
๐ Security concerns Prototype Pollution: While the code includes tests for prototype pollution prevention, the merge function implementation could still be vulnerable to prototype pollution attacks through nested object properties that bypass the isPlainObject check. Additional validation may be needed for nested object properties. |
โก Recommended focus areas for review Possible Bug The merge function may not properly handle circular references in objects that are not caught by WeakMap tracking Performance Issue The isPlainObject implementation checks multiple Symbol properties which could be optimized by checking prototype chain first Code Smell The _deepClone helper function is not exported but contains complex logic that should be tested independently |
This pull request is automatically built and testable in CodeSandbox.
To see build info of the built libraries, click here or the icon next to each commit SHA.
Attention: Patch coverage is 91.89189%
with 3 lines
in your changes missing coverage. Please review.
Project coverage is 78.41%. Comparing base (
0b8452b
) to head (90c3790
). Report is 1 commits behind head on v2.
Files with missing lines | Patch % | Lines |
---|---|---|
packages/utils/src/shared.js | 90.62% | 3 Missing :warning: |
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
๐จ Try these New Features:
On 2024-11-23 07:02:58 +0000, PR #946 (90c3790beb680dde97b03855e7724e1cd5f6f986) was successfully deployed. You can view it at the following link: https://trendmicro-frontend.github.io/tonic-ui-demo/react/pr-946/
/improve
/review
/describe
PR Description updated to latest commit (https://github.com/trendmicro-frontend/tonic-ui/commit/65e7a61799c1a1a5c62d528cd2c6585855f91741)
Latest suggestions up to 2c81177
Category | Suggestion | Score |
Possible issue |
โ Add protection against circular references in deep cloning to prevent stack overflow errors___Suggestion Impact:The commit implemented a check for circular references in the _deepClone function using a WeakMap to prevent infinite recursion, as suggested. code diff: ```diff -const _deepClone = (source) => { +const _deepClone = (source, seen = new WeakMap()) => { + // Use a `WeakMap` to track objects and detect circular references. + // If the object has been cloned before, return the cached cloned version. + if (seen.has(source)) { + return seen.get(source); + } + if (Array.isArray(source)) { - return source.map((item) => _deepClone(item)); + const clonedArray = []; + seen.set(source, clonedArray); + for (let i = 0; i < source.length; ++i) { + clonedArray[i] = _deepClone(source[i], seen); + } + return clonedArray; } if (isPlainObject(source)) { - const output = {}; - Object.keys(source).forEach((key) => { - output[key] = _deepClone(source[key]); - }); - return output; + const clonedObject = {}; + seen.set(source, clonedObject); + for (const [key, value] of Object.entries(source)) { + clonedObject[key] = _deepClone(value, seen); + } + return clonedObject; } ```_deepClone function to prevent infinite recursion when cloning objects with circular dependencies.** [packages/utils/src/shared.js [18-33]](https://github.com/trendmicro-frontend/tonic-ui/pull/946/files#diff-aeb582188d4c096beb54627221b5a143210c728a566d8328830e6d31e1fee116R18-R33) ```diff -const _deepClone = (source) => { - if (Array.isArray(source)) { - return source.map((item) => _deepClone(item)); - } - - if (isPlainObject(source)) { - const output = {}; - Object.keys(source).forEach((key) => { - output[key] = _deepClone(source[key]); - }); +const _deepClone = (source, seen = new WeakMap()) => { + if (typeof source === 'object' && source !== null) { + if (seen.has(source)) { + return seen.get(source); + } + const output = Array.isArray(source) ? [] : {}; + seen.set(source, output); + + if (Array.isArray(source)) { + output.push(...source.map(item => _deepClone(item, seen))); + } else if (isPlainObject(source)) { + Object.keys(source).forEach((key) => { + output[key] = _deepClone(source[key], seen); + }); + } return output; } - return source; }; ``` - [ ] **Apply this suggestion** Suggestion importance[1-10]: 9Why: The suggestion addresses a critical security issue by preventing infinite recursion in deep cloning operations when circular references are present, which could cause stack overflow crashes. | 9 |
Security |
Add depth limit to prevent stack overflow from extremely deep recursive merges___ **Add a size limit check in the merge function to prevent potential stack overflowfrom extremely deep objects.** [packages/utils/src/shared.js [60-65]](https://github.com/trendmicro-frontend/tonic-ui/pull/946/files#diff-aeb582188d4c096beb54627221b5a143210c728a566d8328830e6d31e1fee116R60-R65) ```diff -export const merge = (target, source, options = { clone: true }) => { +export const merge = (target, source, options = { clone: true }, depth = 0) => { + if (depth > 1000) { + throw new Error('Maximum merge depth exceeded'); + } // Merge arrays if (Array.isArray(target) && Array.isArray(source)) { const output = options.clone ? [...target] : target; source.forEach((item, index) => { ``` - [ ] **Apply this suggestion** Suggestion importance[1-10]: 8Why: This suggestion adds an important safety check to prevent potential DoS attacks through maliciously crafted deep objects that could cause stack overflow. | 8 |
Enhancement |
โ Improve code readability and performance by using Object.entries() for object iteration___Suggestion Impact:The suggestion to use Object.entries() instead of Object.keys().forEach() was implemented in the commit, improving code readability and performance. code diff: ```diff + for (const [key, value] of Object.entries(source)) { + if (isPlainObject(value) && Object.prototype.hasOwnProperty.call(output, key) && isPlainObject(output[key])) { + output[key] = merge(output[key], value, options); } else { - output[key] = options.clone ? _deepClone(source[key]) : source[key]; + output[key] = options.clone ? _deepClone(value) : value; } - }); + } ```readability and to avoid accessing object properties inside the loop.** [packages/utils/src/shared.js [77-83]](https://github.com/trendmicro-frontend/tonic-ui/pull/946/files#diff-aeb582188d4c096beb54627221b5a143210c728a566d8328830e6d31e1fee116R77-R83) ```diff -Object.keys(source).forEach((key) => { - if (isPlainObject(source[key]) && isPlainObject(output[key]) && Object.prototype.hasOwnProperty.call(output, key)) { - output[key] = merge(output[key], source[key], options); +for (const [key, value] of Object.entries(source)) { + if (isPlainObject(value) && isPlainObject(output[key]) && Object.prototype.hasOwnProperty.call(output, key)) { + output[key] = merge(output[key], value, options); } else { - output[key] = options.clone ? _deepClone(source[key]) : source[key]; + output[key] = options.clone ? _deepClone(value) : value; } -}); +} ``` - [ ] **Apply this suggestion** Suggestion importance[1-10]: 4Why: While the suggestion improves code readability and slightly optimizes property access, the impact is relatively minor as the current implementation is already functional and clear. | 4 |
Category | Suggestion | Score |
Enhancement |
โ Add special handling for array types in object merging function___Suggestion Impact:The commit added logic to handle merging of arrays in the merge function, which aligns with the suggestion to handle arrays properly. code diff: ```diff + // Merge arrays + if (Array.isArray(target) && Array.isArray(source)) { + const output = options.clone ? [...target] : target; + source.forEach((item, index) => { + if (isPlainObject(item) && isPlainObject(output[index])) { + output[index] = merge(output[index], item, options); } else { - output[key] = source[key]; + output[index] = options.clone ? _deepClone(item) : item; } }); + return output; ```merge function should handle arrays properly since they are also objects but require different merging logic.** [packages/utils/src/shared.js [60-61]](https://github.com/trendmicro-frontend/tonic-ui/pull/946/files#diff-aeb582188d4c096beb54627221b5a143210c728a566d8328830e6d31e1fee116R60-R61) ```diff +if (Array.isArray(source)) { + return options.clone ? [...source] : source; +} if (isPlainObject(target) && isPlainObject(source)) { Object.keys(source).forEach((key) => { ``` Suggestion importance[1-10]: 8Why: The suggestion addresses an important edge case in the merge function by properly handling arrays, which could prevent unexpected behavior when merging array-containing objects. | 8 |
Possible bug |
Add defensive programming by validating input parameters before processing___ **Consider adding a null/undefined check for the source parameter to prevent potentialruntime errors when merging objects.** [packages/utils/src/shared.js [57-58]](https://github.com/trendmicro-frontend/tonic-ui/pull/946/files#diff-aeb582188d4c096beb54627221b5a143210c728a566d8328830e6d31e1fee116R57-R58) ```diff export const merge = (target, source, options = { clone: true }) => { + if (!source) return target; const output = options.clone ? { ...target } : target; ``` Suggestion importance[1-10]: 7Why: Adding a null/undefined check for the source parameter is a good defensive programming practice that prevents potential runtime errors and improves code robustness. | 7 |
Best practice |
Improve type checking accuracy for plain objects___ **Add a check for primitive values wrapped in Object to ensure more accurate plainobject detection.** [packages/utils/src/assertion.js [34-37]](https://github.com/trendmicro-frontend/tonic-ui/pull/946/files#diff-9bb98fab345c19425bb6bf765ba09c993de88b5f9fd1e21b6a8023d23bebcef3R34-R37) ```diff export const isPlainObject = (value) => { - if (typeof value !== 'object' || value === null) { + if (typeof value !== 'object' || value === null || Object(value) !== value) { return false; } ``` Suggestion importance[1-10]: 6Why: The additional check for primitive values wrapped in Object would make the isPlainObject function more robust, though the current implementation already handles most common cases correctly. | 6 |
Category | Suggestion | Score |
Possible bug |
Add input validation to prevent errors from invalid arguments___ **Add input validation to ensure target and source are objects before proceeding withthe merge. This prevents potential errors when invalid inputs are provided.** [packages/utils/src/shared.js [57-58]](https://github.com/trendmicro-frontend/tonic-ui/pull/946/files#diff-aeb582188d4c096beb54627221b5a143210c728a566d8328830e6d31e1fee116R57-R58) ```diff export const deepmerge = (target, source, options = { clone: true }) => { + if (!target || typeof target !== 'object') return source; + if (!source || typeof source !== 'object') return target; const output = options.clone ? { ...target } : target; ``` Suggestion importance[1-10]: 8Why: The suggestion adds crucial input validation to prevent potential runtime errors and unexpected behavior when invalid arguments are passed to the deepmerge function. | 8 |
Best practice |
Use modern Object.hasOwn() method for property ownership checks___ **Consider using Object.hasOwn() instead of Object.prototype.hasOwnProperty.call() fora more modern and safer property ownership check.** [packages/utils/src/shared.js [64]](https://github.com/trendmicro-frontend/tonic-ui/pull/946/files#diff-aeb582188d4c096beb54627221b5a143210c728a566d8328830e6d31e1fee116R64-R64) ```diff -Object.prototype.hasOwnProperty.call(target, key) +Object.hasOwn(target, key) ``` Suggestion importance[1-10]: 5Why: Using Object.hasOwn() is a cleaner and more modern approach than Object.prototype.hasOwnProperty.call(), though both are functionally correct. The improvement is mainly for code maintainability. | 5 |
Performance |
Cache repeated function call results to improve performance___ **Cache the result of Object.getPrototypeOf(value) to avoid multiple calls to the samefunction with the same argument.** [packages/utils/src/assertion.js [39-40]](https://github.com/trendmicro-frontend/tonic-ui/pull/946/files#diff-9bb98fab345c19425bb6bf765ba09c993de88b5f9fd1e21b6a8023d23bebcef3R39-R40) ```diff const prototype = Object.getPrototypeOf(value); -return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value); +const prototypeOfPrototype = prototype && Object.getPrototypeOf(prototype); +return (prototype === null || prototype === Object.prototype || prototypeOfPrototype === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value); ``` Suggestion importance[1-10]: 4Why: The suggestion optimizes performance by avoiding a repeated Object.getPrototypeOf() call, though the performance impact would be minimal in most use cases. | 4 |
/review
/improve
No code suggestions found for the PR.
/ask enhance test coverae for the merge
function with arrays
enhance test coverae for the merge
function with arrays
To enhance test coverage for the merge
function specifically with arrays, you can add test cases that cover various scenarios involving array merging. Here are some suggestions for additional test cases:
Merging Arrays with Primitive Values:
it('should merge arrays with primitive values', () => {
const target = [1, 2, 3];
const source = [4, 5, 6];
const result = merge(target, source);
expect(result).toEqual([4, 5, 6]);
});
Merging Arrays with Objects:
it('should merge arrays with objects', () => {
const target = [{ a: 1 }, { b: 2 }];
const source = [{ a: 3 }, { c: 4 }];
const result = merge(target, source);
expect(result).toEqual([{ a: 3 }, { c: 4 }]);
});
Handling Arrays of Different Lengths:
it('should handle arrays of different lengths', () => {
const target = [1, 2];
const source = [3, 4, 5];
const result = merge(target, source);
expect(result).toEqual([3, 4, 5]);
});
Merging Nested Arrays:
it('should merge nested arrays', () => {
const target = [[1, 2], [3, 4]];
const source = [[5, 6], [7, 8]];
const result = merge(target, source);
expect(result).toEqual([[5, 6], [7, 8]]);
});
Ensuring Cloning Behavior:
it('should clone arrays when merging', () => {
const target = [1, 2, 3];
const source = [4, 5, 6];
const result = merge(target, source, { clone: true });
result[0] = 10;
expect(target).toEqual([1, 2, 3]);
expect(source).toEqual([4, 5, 6]);
});
By adding these test cases, you can ensure that the merge
function behaves correctly when dealing with arrays in various scenarios.
PR Type
enhancement, tests
Description
merge
function inshared.js
to merge objects with deep clone capability.isPlainObject
function inassertion.js
to check if a value is a plain object.merge
andisPlainObject
.isEmptyArray
,isEmptyObject
,ariaAttr
, anddataAttr
.Changes walkthrough ๐
assertion.js
Add `isPlainObject` function to assertion utilities
packages/utils/src/assertion.js
isPlainObject
function to check if a value is a plain object.shared.js
Add `merge` function with deep clone capability
packages/utils/src/shared.js
merge
function to merge objects.assertion.test.js
Add tests for `isPlainObject` and refine existing tests
packages/utils/src/__tests__/assertion.test.js
isPlainObject
.isEmptyArray
andisEmptyObject
.shared.test.js
Add tests for `merge` function and refine attribute tests
packages/utils/src/__tests__/shared.test.js
merge
function.ariaAttr
anddataAttr
.index.test.js
Update export tests to include new utilities
packages/utils/__tests__/index.test.js - Updated expected exports to include `isPlainObject` and `merge`.
tonic-ui-945-utils.md
Document new utility functions in changeset
.changeset/tonic-ui-945-utils.md - Documented the addition of `merge` and `isPlainObject` functions.