-
Notifications
You must be signed in to change notification settings - Fork 34
Add polyfills for new Set methods (union, intersection, difference, symmetricDifference, isSubsetOf, isSupersetOf, isDisjointFrom) #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+281
−0
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| // Set-like interface per TC39 spec: must have has, keys, and size | ||
| interface SetLike<T> { | ||
| has(value: T): boolean | ||
| keys(): IterableIterator<T> | ||
| readonly size: number | ||
| } | ||
|
|
||
| /*#__PURE__*/ | ||
| export function union<T>(this: Set<T>, other: SetLike<T>): Set<T> { | ||
| const result = new Set<T>(this) | ||
| for (const value of other.keys()) { | ||
| result.add(value) | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| /*#__PURE__*/ | ||
| export function intersection<T>(this: Set<T>, other: SetLike<T>): Set<T> { | ||
| const result = new Set<T>() | ||
| for (const value of this) { | ||
| if (other.has(value)) { | ||
| result.add(value) | ||
| } | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| /*#__PURE__*/ | ||
| export function difference<T>(this: Set<T>, other: SetLike<T>): Set<T> { | ||
| const result = new Set<T>() | ||
| for (const value of this) { | ||
| if (!other.has(value)) { | ||
| result.add(value) | ||
| } | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| /*#__PURE__*/ | ||
| export function symmetricDifference<T>(this: Set<T>, other: SetLike<T>): Set<T> { | ||
| const result = new Set<T>(this) | ||
| for (const value of other.keys()) { | ||
| if (result.has(value)) { | ||
| result.delete(value) | ||
| } else { | ||
| result.add(value) | ||
| } | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| /*#__PURE__*/ | ||
| export function isSubsetOf<T>(this: Set<T>, other: SetLike<T>): boolean { | ||
| if (this.size > other.size) return false | ||
| for (const value of this) { | ||
| if (!other.has(value)) { | ||
| return false | ||
| } | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| /*#__PURE__*/ | ||
| export function isSupersetOf<T>(this: Set<T>, other: SetLike<T>): boolean { | ||
| for (const value of other.keys()) { | ||
| if (!this.has(value)) { | ||
| return false | ||
| } | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| /*#__PURE__*/ | ||
| export function isDisjointFrom<T>(this: Set<T>, other: SetLike<T>): boolean { | ||
| if (this.size <= other.size) { | ||
| for (const value of this) { | ||
| if (other.has(value)) { | ||
| return false | ||
| } | ||
| } | ||
| } else { | ||
| for (const value of other.keys()) { | ||
| if (this.has(value)) { | ||
| return false | ||
| } | ||
| } | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| /*#__PURE__*/ | ||
| export function isSupported(): boolean { | ||
| return ( | ||
| 'union' in Set.prototype && | ||
| 'intersection' in Set.prototype && | ||
| 'difference' in Set.prototype && | ||
| 'symmetricDifference' in Set.prototype && | ||
| 'isSubsetOf' in Set.prototype && | ||
| 'isSupersetOf' in Set.prototype && | ||
| 'isDisjointFrom' in Set.prototype | ||
| ) | ||
| } | ||
|
|
||
| /*#__PURE__*/ | ||
| export function isPolyfilled(): boolean { | ||
| const proto = Set.prototype as unknown as Record<string, unknown> | ||
| return ( | ||
| 'union' in Set.prototype && | ||
| proto['union'] === union && | ||
| proto['intersection'] === intersection && | ||
| proto['difference'] === difference && | ||
| proto['symmetricDifference'] === symmetricDifference && | ||
| proto['isSubsetOf'] === isSubsetOf && | ||
| proto['isSupersetOf'] === isSupersetOf && | ||
| proto['isDisjointFrom'] === isDisjointFrom | ||
| ) | ||
| } | ||
|
|
||
| export function apply(): void { | ||
| if (!isSupported()) { | ||
| Object.assign(Set.prototype, { | ||
| union, | ||
| intersection, | ||
| difference, | ||
| symmetricDifference, | ||
| isSubsetOf, | ||
| isSupersetOf, | ||
| isDisjointFrom, | ||
| }) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| import {expect} from 'chai' | ||
| import { | ||
| apply, | ||
| isPolyfilled, | ||
| isSupported, | ||
| union, | ||
| intersection, | ||
| difference, | ||
| symmetricDifference, | ||
| isSubsetOf, | ||
| isSupersetOf, | ||
| isDisjointFrom, | ||
| } from '../src/set-methods.ts' | ||
|
|
||
| // eslint-disable-next-line i18n-text/no-en | ||
| describe('Set methods', () => { | ||
| it('has standard isSupported, isPolyfilled, apply API', () => { | ||
| expect(isSupported).to.be.a('function') | ||
| expect(isPolyfilled).to.be.a('function') | ||
| expect(apply).to.be.a('function') | ||
| expect(isSupported()).to.be.a('boolean') | ||
| expect(isPolyfilled()).to.equal(false) | ||
| }) | ||
|
|
||
| describe('union', () => { | ||
| it('returns a new Set with elements from both sets', () => { | ||
| const a = new Set([1, 2, 3]) | ||
| const b = new Set([3, 4, 5]) | ||
| const result = union.call(a, b) | ||
| expect(result).to.be.instanceof(Set) | ||
| expect([...result].sort()).to.eql([1, 2, 3, 4, 5]) | ||
| }) | ||
|
|
||
| it('handles empty sets', () => { | ||
| const a = new Set([1, 2]) | ||
| const result = union.call(a, new Set()) | ||
| expect([...result].sort()).to.eql([1, 2]) | ||
| }) | ||
| }) | ||
|
|
||
| describe('intersection', () => { | ||
| it('returns a new Set with elements in both sets', () => { | ||
| const a = new Set([1, 2, 3]) | ||
| const b = new Set([2, 3, 4]) | ||
| const result = intersection.call(a, b) | ||
| expect(result).to.be.instanceof(Set) | ||
| expect([...result].sort()).to.eql([2, 3]) | ||
| }) | ||
|
|
||
| it('returns empty set when no common elements', () => { | ||
| const a = new Set([1, 2]) | ||
| const b = new Set([3, 4]) | ||
| const result = intersection.call(a, b) | ||
| expect(result.size).to.equal(0) | ||
| }) | ||
| }) | ||
|
|
||
| describe('difference', () => { | ||
| it('returns a new Set with elements in this but not other', () => { | ||
| const a = new Set([1, 2, 3]) | ||
| const b = new Set([2, 3, 4]) | ||
| const result = difference.call(a, b) | ||
| expect(result).to.be.instanceof(Set) | ||
| expect([...result]).to.eql([1]) | ||
| }) | ||
|
|
||
| it('returns a copy of this when no overlap', () => { | ||
| const a = new Set([1, 2]) | ||
| const b = new Set([3, 4]) | ||
| const result = difference.call(a, b) | ||
| expect([...result].sort()).to.eql([1, 2]) | ||
| }) | ||
| }) | ||
|
|
||
| describe('symmetricDifference', () => { | ||
| it('returns elements in one set but not both', () => { | ||
| const a = new Set([1, 2, 3]) | ||
| const b = new Set([2, 3, 4]) | ||
| const result = symmetricDifference.call(a, b) | ||
| expect(result).to.be.instanceof(Set) | ||
| expect([...result].sort()).to.eql([1, 4]) | ||
| }) | ||
|
|
||
| it('returns empty set for identical sets', () => { | ||
| const a = new Set([1, 2]) | ||
| const result = symmetricDifference.call(a, new Set([1, 2])) | ||
| expect(result.size).to.equal(0) | ||
| }) | ||
| }) | ||
|
|
||
| describe('isSubsetOf', () => { | ||
| it('returns true when all elements are in the other set', () => { | ||
| const a = new Set([1, 2]) | ||
| const b = new Set([1, 2, 3]) | ||
| expect(isSubsetOf.call(a, b)).to.equal(true) | ||
| }) | ||
|
|
||
| it('returns false when some elements are not in the other set', () => { | ||
| const a = new Set([1, 2, 4]) | ||
| const b = new Set([1, 2, 3]) | ||
| expect(isSubsetOf.call(a, b)).to.equal(false) | ||
| }) | ||
|
|
||
| it('returns true for empty set', () => { | ||
| const a = new Set() | ||
| const b = new Set([1, 2]) | ||
| expect(isSubsetOf.call(a, b)).to.equal(true) | ||
| }) | ||
| }) | ||
|
|
||
| describe('isSupersetOf', () => { | ||
| it('returns true when this set contains all elements of other', () => { | ||
| const a = new Set([1, 2, 3]) | ||
| const b = new Set([1, 2]) | ||
| expect(isSupersetOf.call(a, b)).to.equal(true) | ||
| }) | ||
|
|
||
| it('returns false when other has elements not in this', () => { | ||
| const a = new Set([1, 2]) | ||
| const b = new Set([1, 2, 3]) | ||
| expect(isSupersetOf.call(a, b)).to.equal(false) | ||
| }) | ||
|
|
||
| it('returns true when other is empty', () => { | ||
| const a = new Set([1, 2]) | ||
| expect(isSupersetOf.call(a, new Set())).to.equal(true) | ||
| }) | ||
| }) | ||
|
|
||
| describe('isDisjointFrom', () => { | ||
| it('returns true when sets have no common elements', () => { | ||
| const a = new Set([1, 2]) | ||
| const b = new Set([3, 4]) | ||
| expect(isDisjointFrom.call(a, b)).to.equal(true) | ||
| }) | ||
|
|
||
| it('returns false when sets share elements', () => { | ||
| const a = new Set([1, 2, 3]) | ||
| const b = new Set([3, 4]) | ||
| expect(isDisjointFrom.call(a, b)).to.equal(false) | ||
| }) | ||
|
|
||
| it('returns true when either set is empty', () => { | ||
| const a = new Set([1, 2]) | ||
| expect(isDisjointFrom.call(a, new Set())).to.equal(true) | ||
| }) | ||
| }) | ||
| }) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are not adding this check:
proto['union'] === union &&toisSupported. Is that intended?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, I believe that's right, if I understand it correctly?
isSupported supports both polyfilled and not, so it should cover both cases (once the polyfill is applied), while isPolyfilled validates whether the support came from the polyfill applied or by built in support (which I think for us is everything but safari 16 or 17)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, yeah that makes sense!