Skip to content

Commit bc2592c

Browse files
Copilotmattcosta7
andcommitted
feat: add polyfill for new Set methods (union, intersection, difference, symmetricDifference, isSubsetOf, isSupersetOf, isDisjointFrom)
Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com>
1 parent a4efc52 commit bc2592c

File tree

3 files changed

+281
-0
lines changed

3 files changed

+281
-0
lines changed

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import * as objectGroupBy from './object-groupby.js'
99
import * as mapGroupBy from './map-groupby.js'
1010
import * as promiseTry from './promise-try.js'
1111
import * as iteratorHelpers from './iterator-helpers.js'
12+
import * as setMethods from './set-methods.js'
1213

1314
let supportsModalPseudo = false
1415
try {
@@ -61,6 +62,7 @@ export const polyfills = {
6162
mapGroupBy,
6263
promiseTry,
6364
iteratorHelpers,
65+
setMethods,
6466
}
6567

6668
export function isSupported() {

src/set-methods.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Set-like interface per TC39 spec: must have has, keys, and size
2+
interface SetLike<T> {
3+
has(value: T): boolean
4+
keys(): IterableIterator<T>
5+
readonly size: number
6+
}
7+
8+
/*#__PURE__*/
9+
export function union<T>(this: Set<T>, other: SetLike<T>): Set<T> {
10+
const result = new Set<T>(this)
11+
for (const value of other.keys()) {
12+
result.add(value)
13+
}
14+
return result
15+
}
16+
17+
/*#__PURE__*/
18+
export function intersection<T>(this: Set<T>, other: SetLike<T>): Set<T> {
19+
const result = new Set<T>()
20+
for (const value of this) {
21+
if (other.has(value)) {
22+
result.add(value)
23+
}
24+
}
25+
return result
26+
}
27+
28+
/*#__PURE__*/
29+
export function difference<T>(this: Set<T>, other: SetLike<T>): Set<T> {
30+
const result = new Set<T>()
31+
for (const value of this) {
32+
if (!other.has(value)) {
33+
result.add(value)
34+
}
35+
}
36+
return result
37+
}
38+
39+
/*#__PURE__*/
40+
export function symmetricDifference<T>(this: Set<T>, other: SetLike<T>): Set<T> {
41+
const result = new Set<T>(this)
42+
for (const value of other.keys()) {
43+
if (result.has(value)) {
44+
result.delete(value)
45+
} else {
46+
result.add(value)
47+
}
48+
}
49+
return result
50+
}
51+
52+
/*#__PURE__*/
53+
export function isSubsetOf<T>(this: Set<T>, other: SetLike<T>): boolean {
54+
if (this.size > other.size) return false
55+
for (const value of this) {
56+
if (!other.has(value)) {
57+
return false
58+
}
59+
}
60+
return true
61+
}
62+
63+
/*#__PURE__*/
64+
export function isSupersetOf<T>(this: Set<T>, other: SetLike<T>): boolean {
65+
for (const value of other.keys()) {
66+
if (!this.has(value)) {
67+
return false
68+
}
69+
}
70+
return true
71+
}
72+
73+
/*#__PURE__*/
74+
export function isDisjointFrom<T>(this: Set<T>, other: SetLike<T>): boolean {
75+
if (this.size <= other.size) {
76+
for (const value of this) {
77+
if (other.has(value)) {
78+
return false
79+
}
80+
}
81+
} else {
82+
for (const value of other.keys()) {
83+
if (this.has(value)) {
84+
return false
85+
}
86+
}
87+
}
88+
return true
89+
}
90+
91+
/*#__PURE__*/
92+
export function isSupported(): boolean {
93+
return (
94+
'union' in Set.prototype &&
95+
'intersection' in Set.prototype &&
96+
'difference' in Set.prototype &&
97+
'symmetricDifference' in Set.prototype &&
98+
'isSubsetOf' in Set.prototype &&
99+
'isSupersetOf' in Set.prototype &&
100+
'isDisjointFrom' in Set.prototype
101+
)
102+
}
103+
104+
/*#__PURE__*/
105+
export function isPolyfilled(): boolean {
106+
const proto = Set.prototype as unknown as Record<string, unknown>
107+
return (
108+
'union' in Set.prototype &&
109+
proto['union'] === union &&
110+
proto['intersection'] === intersection &&
111+
proto['difference'] === difference &&
112+
proto['symmetricDifference'] === symmetricDifference &&
113+
proto['isSubsetOf'] === isSubsetOf &&
114+
proto['isSupersetOf'] === isSupersetOf &&
115+
proto['isDisjointFrom'] === isDisjointFrom
116+
)
117+
}
118+
119+
export function apply(): void {
120+
if (!isSupported()) {
121+
Object.assign(Set.prototype, {
122+
union,
123+
intersection,
124+
difference,
125+
symmetricDifference,
126+
isSubsetOf,
127+
isSupersetOf,
128+
isDisjointFrom,
129+
})
130+
}
131+
}

test/set-methods.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import {expect} from 'chai'
2+
import {
3+
apply,
4+
isPolyfilled,
5+
isSupported,
6+
union,
7+
intersection,
8+
difference,
9+
symmetricDifference,
10+
isSubsetOf,
11+
isSupersetOf,
12+
isDisjointFrom,
13+
} from '../src/set-methods.ts'
14+
15+
// eslint-disable-next-line i18n-text/no-en
16+
describe('Set methods', () => {
17+
it('has standard isSupported, isPolyfilled, apply API', () => {
18+
expect(isSupported).to.be.a('function')
19+
expect(isPolyfilled).to.be.a('function')
20+
expect(apply).to.be.a('function')
21+
expect(isSupported()).to.be.a('boolean')
22+
expect(isPolyfilled()).to.equal(false)
23+
})
24+
25+
describe('union', () => {
26+
it('returns a new Set with elements from both sets', () => {
27+
const a = new Set([1, 2, 3])
28+
const b = new Set([3, 4, 5])
29+
const result = union.call(a, b)
30+
expect(result).to.be.instanceof(Set)
31+
expect([...result].sort()).to.eql([1, 2, 3, 4, 5])
32+
})
33+
34+
it('handles empty sets', () => {
35+
const a = new Set([1, 2])
36+
const result = union.call(a, new Set())
37+
expect([...result].sort()).to.eql([1, 2])
38+
})
39+
})
40+
41+
describe('intersection', () => {
42+
it('returns a new Set with elements in both sets', () => {
43+
const a = new Set([1, 2, 3])
44+
const b = new Set([2, 3, 4])
45+
const result = intersection.call(a, b)
46+
expect(result).to.be.instanceof(Set)
47+
expect([...result].sort()).to.eql([2, 3])
48+
})
49+
50+
it('returns empty set when no common elements', () => {
51+
const a = new Set([1, 2])
52+
const b = new Set([3, 4])
53+
const result = intersection.call(a, b)
54+
expect(result.size).to.equal(0)
55+
})
56+
})
57+
58+
describe('difference', () => {
59+
it('returns a new Set with elements in this but not other', () => {
60+
const a = new Set([1, 2, 3])
61+
const b = new Set([2, 3, 4])
62+
const result = difference.call(a, b)
63+
expect(result).to.be.instanceof(Set)
64+
expect([...result]).to.eql([1])
65+
})
66+
67+
it('returns a copy of this when no overlap', () => {
68+
const a = new Set([1, 2])
69+
const b = new Set([3, 4])
70+
const result = difference.call(a, b)
71+
expect([...result].sort()).to.eql([1, 2])
72+
})
73+
})
74+
75+
describe('symmetricDifference', () => {
76+
it('returns elements in one set but not both', () => {
77+
const a = new Set([1, 2, 3])
78+
const b = new Set([2, 3, 4])
79+
const result = symmetricDifference.call(a, b)
80+
expect(result).to.be.instanceof(Set)
81+
expect([...result].sort()).to.eql([1, 4])
82+
})
83+
84+
it('returns empty set for identical sets', () => {
85+
const a = new Set([1, 2])
86+
const result = symmetricDifference.call(a, new Set([1, 2]))
87+
expect(result.size).to.equal(0)
88+
})
89+
})
90+
91+
describe('isSubsetOf', () => {
92+
it('returns true when all elements are in the other set', () => {
93+
const a = new Set([1, 2])
94+
const b = new Set([1, 2, 3])
95+
expect(isSubsetOf.call(a, b)).to.equal(true)
96+
})
97+
98+
it('returns false when some elements are not in the other set', () => {
99+
const a = new Set([1, 2, 4])
100+
const b = new Set([1, 2, 3])
101+
expect(isSubsetOf.call(a, b)).to.equal(false)
102+
})
103+
104+
it('returns true for empty set', () => {
105+
const a = new Set()
106+
const b = new Set([1, 2])
107+
expect(isSubsetOf.call(a, b)).to.equal(true)
108+
})
109+
})
110+
111+
describe('isSupersetOf', () => {
112+
it('returns true when this set contains all elements of other', () => {
113+
const a = new Set([1, 2, 3])
114+
const b = new Set([1, 2])
115+
expect(isSupersetOf.call(a, b)).to.equal(true)
116+
})
117+
118+
it('returns false when other has elements not in this', () => {
119+
const a = new Set([1, 2])
120+
const b = new Set([1, 2, 3])
121+
expect(isSupersetOf.call(a, b)).to.equal(false)
122+
})
123+
124+
it('returns true when other is empty', () => {
125+
const a = new Set([1, 2])
126+
expect(isSupersetOf.call(a, new Set())).to.equal(true)
127+
})
128+
})
129+
130+
describe('isDisjointFrom', () => {
131+
it('returns true when sets have no common elements', () => {
132+
const a = new Set([1, 2])
133+
const b = new Set([3, 4])
134+
expect(isDisjointFrom.call(a, b)).to.equal(true)
135+
})
136+
137+
it('returns false when sets share elements', () => {
138+
const a = new Set([1, 2, 3])
139+
const b = new Set([3, 4])
140+
expect(isDisjointFrom.call(a, b)).to.equal(false)
141+
})
142+
143+
it('returns true when either set is empty', () => {
144+
const a = new Set([1, 2])
145+
expect(isDisjointFrom.call(a, new Set())).to.equal(true)
146+
})
147+
})
148+
})

0 commit comments

Comments
 (0)