From 31a15a1483e0af0f9ede24de0a7f1d24bf9d408d Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Mon, 27 Jan 2025 11:20:56 +0100 Subject: [PATCH] feat: add generator detection functions to utils (#2923) Functions like this are used in several places in the stack so move them to the utils package for reuse. Incorporates changes from #2918 --- packages/utils/package.json | 8 ++++ packages/utils/src/is-async-generator.ts | 10 +++++ packages/utils/src/is-generator.ts | 8 ++++ .../utils/test/is-async-generator.spec.ts | 41 +++++++++++++++++++ packages/utils/test/is-generator.spec.ts | 41 +++++++++++++++++++ packages/utils/test/is-promise.spec.ts | 28 +++++++++++++ packages/utils/typedoc.json | 2 + 7 files changed, 138 insertions(+) create mode 100644 packages/utils/src/is-async-generator.ts create mode 100644 packages/utils/src/is-generator.ts create mode 100644 packages/utils/test/is-async-generator.spec.ts create mode 100644 packages/utils/test/is-generator.spec.ts diff --git a/packages/utils/package.json b/packages/utils/package.json index d332774823..cbf42adbd9 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -84,6 +84,14 @@ "types": "./dist/src/ip-port-to-multiaddr.d.ts", "import": "./dist/src/ip-port-to-multiaddr.js" }, + "./is-async-generator": { + "types": "./dist/src/is-async-generator.d.ts", + "import": "./dist/src/is-async-generator.js" + }, + "./is-generator": { + "types": "./dist/src/is-generator.d.ts", + "import": "./dist/src/is-generator.js" + }, "./is-promise": { "types": "./dist/src/is-promise.d.ts", "import": "./dist/src/is-promise.js" diff --git a/packages/utils/src/is-async-generator.ts b/packages/utils/src/is-async-generator.ts new file mode 100644 index 0000000000..73a7a20b6b --- /dev/null +++ b/packages/utils/src/is-async-generator.ts @@ -0,0 +1,10 @@ +export function isAsyncGenerator (obj: unknown): obj is AsyncGenerator { + if (obj == null) return false + const asyncIterator = (obj as { [Symbol.asyncIterator]?: unknown })?.[ + Symbol.asyncIterator + ] + if (typeof asyncIterator !== 'function') return false + + const instance = obj as { next?: unknown } + return typeof instance.next === 'function' +} diff --git a/packages/utils/src/is-generator.ts b/packages/utils/src/is-generator.ts new file mode 100644 index 0000000000..ee5825412d --- /dev/null +++ b/packages/utils/src/is-generator.ts @@ -0,0 +1,8 @@ +export function isGenerator (obj: unknown): obj is Generator { + if (obj == null) return false + const iterator = (obj as { [Symbol.iterator]?: unknown })?.[Symbol.iterator] + if (typeof iterator !== 'function') return false + + const instance = obj as { next?: unknown } + return typeof instance.next === 'function' +} diff --git a/packages/utils/test/is-async-generator.spec.ts b/packages/utils/test/is-async-generator.spec.ts new file mode 100644 index 0000000000..09531aa0eb --- /dev/null +++ b/packages/utils/test/is-async-generator.spec.ts @@ -0,0 +1,41 @@ +import { expect } from 'aegir/chai' +import { isAsyncGenerator } from '../src/is-async-generator.js' + +describe('is-async-generator', () => { + it('should return true if the value is an async generator', () => { + async function * asyncGen (): AsyncGenerator { + yield 1 + } + const asyncGenerator = asyncGen() + expect(isAsyncGenerator(asyncGenerator)).to.be.true() + + const asyncGenObj = (async function * () { + yield 1 + })() + expect(isAsyncGenerator(asyncGenObj)).to.be.true() + }) + + it('should return false if the value is not an async generator', () => { + expect(isAsyncGenerator(1)).to.be.false() + expect(isAsyncGenerator('string')).to.be.false() + expect(isAsyncGenerator({})).to.be.false() + expect(isAsyncGenerator([])).to.be.false() + expect(isAsyncGenerator(null)).to.be.false() + expect(isAsyncGenerator(undefined)).to.be.false() + expect(isAsyncGenerator(() => {})).to.be.false() + expect(isAsyncGenerator(async () => {})).to.be.false() + expect( + isAsyncGenerator(function * () { + yield 1 + }) + ).to.be.false() + expect( + isAsyncGenerator(async function * () { + yield 1 + }) + ).to.be.false() // async generator function, not generator + expect(isAsyncGenerator(Promise.resolve())).to.be.false() + expect(isAsyncGenerator({ next: async () => {} })).to.be.false() + expect(isAsyncGenerator({ [Symbol.asyncIterator]: () => {} })).to.be.false() + }) +}) diff --git a/packages/utils/test/is-generator.spec.ts b/packages/utils/test/is-generator.spec.ts new file mode 100644 index 0000000000..aaa2d6bf65 --- /dev/null +++ b/packages/utils/test/is-generator.spec.ts @@ -0,0 +1,41 @@ +import { expect } from 'aegir/chai' +import { isGenerator } from '../src/is-generator.js' + +describe('is-generator', () => { + it('should return true if the value is a generator', () => { + function * gen (): Generator { + yield 1 + } + const generator = gen() + expect(isGenerator(generator)).to.be.true() + + const genObj = (function * () { + yield 1 + })() + expect(isGenerator(genObj)).to.be.true() + }) + + it('should return false if the value is not a generator', () => { + expect(isGenerator(1)).to.be.false() + expect(isGenerator('string')).to.be.false() + expect(isGenerator({})).to.be.false() + expect(isGenerator([])).to.be.false() + expect(isGenerator(null)).to.be.false() + expect(isGenerator(undefined)).to.be.false() + expect(isGenerator(() => {})).to.be.false() + expect(isGenerator(async () => {})).to.be.false() + expect( + isGenerator(function * () { + yield 1 + }) + ).to.be.false() // generator function, not generator + expect( + isGenerator(async function * () { + yield 1 + }) + ).to.be.false() + expect(isGenerator(Promise.resolve())).to.be.false() + expect(isGenerator({ next: () => {} })).to.be.false() + expect(isGenerator({ [Symbol.iterator]: () => {} })).to.be.false() + }) +}) diff --git a/packages/utils/test/is-promise.spec.ts b/packages/utils/test/is-promise.spec.ts index fda0cc33ed..a7c93f9360 100644 --- a/packages/utils/test/is-promise.spec.ts +++ b/packages/utils/test/is-promise.spec.ts @@ -31,4 +31,32 @@ describe('is-promise', () => { it('should not detect partial promise', () => { expect(isPromise({ then: true })).to.be.false() }) + + it('should return true if the value is a promise', () => { + expect(isPromise(Promise.resolve())).to.be.true() + expect(isPromise(new Promise(() => {}))).to.be.true() + expect(isPromise(Promise.reject(new Error('test')))).to.be.true() + }) + + it('should return false if the value is not a promise', () => { + expect(isPromise(1)).to.be.false() + expect(isPromise('string')).to.be.false() + expect(isPromise({})).to.be.false() + expect(isPromise([])).to.be.false() + expect(isPromise(null)).to.be.false() + expect(isPromise(undefined)).to.be.false() + expect(isPromise(() => {})).to.be.false() + expect(isPromise(async () => {})).to.be.false() + expect( + isPromise(function * () { + yield 1 + }) + ).to.be.false() + expect( + isPromise(async function * () { + yield 1 + }) + ).to.be.false() + expect(isPromise({ then: 1 })).to.be.false() + }) }) diff --git a/packages/utils/typedoc.json b/packages/utils/typedoc.json index 2ee13de9f3..aa423657bc 100644 --- a/packages/utils/typedoc.json +++ b/packages/utils/typedoc.json @@ -11,6 +11,8 @@ "./src/filters/index.ts", "./src/global-unicast-ip.ts", "./src/ip-port-to-multiaddr.ts", + "./src/is-async-generator.ts", + "./src/is-generator.ts", "./src/is-promise.ts", "./src/link-local-ip.ts", "./src/moving-average.ts",