Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/shared/src/object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,37 @@ describe('set', () => {
expect(root.a).toBe(date)
expect((root.a as Record<string, unknown>).b).toBe('value')
})

it('does not pollute the prototype via __proto__', () => {
const root: Record<string, unknown> = {}
set(root, ['__proto__', 'polluted'], 'yes')

expect(({} as Record<string, unknown>).polluted).toBeUndefined()
expect(Object.getPrototypeOf(root)).toBe(Object.prototype)
expect(Object.hasOwn(root, '__proto__')).toBe(true)
// eslint-disable-next-line no-proto, no-restricted-properties
expect((root as any).__proto__).toEqual({ polluted: 'yes' })
})

it('does not pollute the prototype via constructor.prototype', () => {
const root: Record<string, unknown> = {}
set(root, ['constructor', 'prototype', 'polluted'], 'yes')

expect(({} as Record<string, unknown>).polluted).toBeUndefined()
expect((Object.prototype as any).polluted).toBeUndefined()
expect(Object.hasOwn(root, 'constructor')).toBe(true)
expect(root.constructor).toEqual({ prototype: { polluted: 'yes' } })
})

it('sets __proto__ as an own property instead of changing the prototype', () => {
const root: Record<string, unknown> = {}
set(root, ['__proto__'], 'value')

expect(Object.getPrototypeOf(root)).toBe(Object.prototype)
expect(Object.hasOwn(root, '__proto__')).toBe(true)
// eslint-disable-next-line no-proto, no-restricted-properties
expect((root as any).__proto__).toBe('value')
})
})

describe('omit', () => {
Expand Down
22 changes: 17 additions & 5 deletions packages/shared/src/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,28 @@ export function set(

for (let i = 0; i < path.length - 1; i++) {
const key = path[i]!
const next = (current as Record<PropertyKey, unknown>)[key]
const next = Object.hasOwn(current, key) ? (current as Record<PropertyKey, unknown>)[key] : undefined

if (!isTypescriptObject(next)) {
;(current as Record<PropertyKey, unknown>)[key] = {}
const child = {}
defineOwnProperty(current, key, child)
current = child
}
else {
current = next
}

current = (current as Record<PropertyKey, object>)[key]!
}

;(current as Record<PropertyKey, unknown>)[path.at(-1)!] = value
defineOwnProperty(current, path.at(-1)!, value)
}

function defineOwnProperty(object: object, key: PropertyKey, value: unknown): void {
Object.defineProperty(object, key, {
value,
writable: true,
enumerable: true,
configurable: true,
})
}

export function omit<T extends object, K extends keyof T>(
Expand Down
Loading