From 9ab99946f9dc313875733bf8a50655a2e03e4a51 Mon Sep 17 00:00:00 2001 From: Lexus Drumgold Date: Wed, 28 Feb 2024 16:18:23 -0500 Subject: [PATCH] feat(utils): `hasModuleId` Signed-off-by: Lexus Drumgold --- .codecov.yml | 1 + package.json | 3 +- src/utils/__tests__/has-module-id.spec.ts | 126 ++++++++++++++++++++++ src/utils/has-module-id.ts | 58 ++++++++++ src/utils/index.ts | 1 + yarn.lock | 3 +- 6 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 src/utils/__tests__/has-module-id.spec.ts create mode 100644 src/utils/has-module-id.ts diff --git a/.codecov.yml b/.codecov.yml index 122c37f..c83ba0d 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -90,3 +90,4 @@ ignore: profiling: critical_files_paths: - src/utils/get-identifiers.ts + - src/utils/has-module-id.ts diff --git a/package.json b/package.json index 121f470..633a796 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,8 @@ "@flex-development/tutils": "6.0.0-alpha.25", "@rollup/pluginutils": "5.1.0", "devlop": "1.1.0", - "estree-util-visit": "2.0.0" + "estree-util-visit": "2.0.0", + "unist-util-is": "6.0.0" }, "devDependencies": { "@arethetypeswrong/cli": "0.14.1", diff --git a/src/utils/__tests__/has-module-id.spec.ts b/src/utils/__tests__/has-module-id.spec.ts new file mode 100644 index 0000000..79e4e33 --- /dev/null +++ b/src/utils/__tests__/has-module-id.spec.ts @@ -0,0 +1,126 @@ +/** + * @file Unit Tests - hasModuleId + * @module estree-util-unassert/utils/tests/unit/hasModuleId + */ + +import type { Assign } from '@flex-development/tutils' +import { createFilter, type CreateFilter } from '@rollup/pluginutils' +import type { + AwaitExpression, + CallExpression, + Identifier, + ImportDeclaration, + Literal +} from 'estree' +import testSubject from '../has-module-id' + +describe('unit:utils/hasModuleId', () => { + let devlopIdentifier: Identifier + let devlopLiteral: Assign + let filter: ReturnType + + beforeAll(() => { + devlopLiteral = { raw: '\'devlop\'', type: 'Literal', value: 'devlop' } + devlopIdentifier = { name: devlopLiteral.value, type: 'Identifier' } + filter = createFilter(/^devlop$/, [], { resolve: false }) + }) + + it('should return false if node.type is ignored', () => { + expect(testSubject(devlopIdentifier, filter)).to.be.false + }) + + describe('AwaitExpression', () => { + it('should return false if node is not dynamic assertion import', () => { + // Arrange + const node: AwaitExpression = { + argument: { source: devlopIdentifier, type: 'ImportExpression' }, + type: 'AwaitExpression' + } + + // Act + Expect + expect(testSubject(node, filter)).to.be.false + }) + + it('should return true node is dynamic assertion import', () => { + // Arrange + const node: AwaitExpression = { + argument: { source: devlopLiteral, type: 'ImportExpression' }, + type: 'AwaitExpression' + } + + // Act + Expect + expect(testSubject(node, filter)).to.be.true + }) + }) + + describe('CallExpression', () => { + it('should return false if node is not assertion `require` call', () => { + // Arrange + const node: CallExpression = { + arguments: [devlopIdentifier], + callee: { name: 'require', type: 'Identifier' }, + optional: false, + type: 'CallExpression' + } + + // Act + Expect + expect(testSubject(node, filter)).to.be.false + }) + + it('should return true node is assertion `require` call', () => { + // Arrange + const node: CallExpression = { + arguments: [devlopLiteral], + callee: { name: 'require', type: 'Identifier' }, + optional: false, + type: 'CallExpression' + } + + // Act + Expect + expect(testSubject(node, filter)).to.be.true + }) + }) + + describe('ImportDeclaration', () => { + let invariant: Assign + + beforeAll(() => { + invariant = { raw: '\'invariant\'', type: 'Literal', value: 'invariant' } + }) + + it('should return false if node is not assertion import', () => { + // Arrange + const node: ImportDeclaration = { + source: invariant, + specifiers: [ + { + local: { name: invariant.value, type: 'Identifier' }, + type: 'ImportDefaultSpecifier' + } + ], + type: 'ImportDeclaration' + } + + // Act + Expect + expect(testSubject(node, filter)).to.be.false + }) + + it('should return true node is assertion import', () => { + // Arrange + const node: ImportDeclaration = { + source: devlopLiteral, + specifiers: [ + { + imported: { name: 'ok', type: 'Identifier' }, + local: { name: 'ok', type: 'Identifier' }, + type: 'ImportSpecifier' + } + ], + type: 'ImportDeclaration' + } + + // Act + Expect + expect(testSubject(node, filter)).to.be.true + }) + }) +}) diff --git a/src/utils/has-module-id.ts b/src/utils/has-module-id.ts new file mode 100644 index 0000000..3aa31d6 --- /dev/null +++ b/src/utils/has-module-id.ts @@ -0,0 +1,58 @@ +/** + * @file Utilities - hasModuleId + * @module estree-util-unassert/utils/hasModuleId + */ + +import type { Nilable } from '@flex-development/tutils' +import type { + AwaitExpression, + CallExpression, + ImportDeclaration, + Node +} from 'estree' +import { is } from 'unist-util-is' + +/** + * Nodes that can contain assertion module ids. + * + * @internal + */ +type HasModuleId = AwaitExpression | CallExpression | ImportDeclaration + +/** + * Check if `node` contains an assertion module id. + * + * @see {@linkcode HasModuleId} + * @see {@linkcode Node} + * + * @param {Nilable} node - Node to check + * @param {(id: unknown) => boolean} filter - Module id filter + * @return {node is HasModuleId} `true` if `node` contains assertion module id + */ +const hasModuleId = ( + node: Nilable, + filter: (id: unknown) => boolean +): node is HasModuleId => { + switch (true) { + case is(node, 'AwaitExpression'): + return ( + is(node.argument, 'ImportExpression') && + is(node.argument.source, 'Literal') && + filter(node.argument.source.value) + ) + case is(node, 'CallExpression'): + return ( + is(node.callee, 'Identifier') && + node.callee.name === 'require' && + !!node.arguments.length && + is(node.arguments[0], 'Literal') && + filter(node.arguments[0].value) + ) + case is(node, 'ImportDeclaration'): + return is(node.source, 'Literal') && filter(node.source.value) + default: + return false + } +} + +export default hasModuleId diff --git a/src/utils/index.ts b/src/utils/index.ts index 8a116ed..3df4a34 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,4 +4,5 @@ */ export { default as getIdentifiers } from './get-identifiers' +export { default as hasModuleId } from './has-module-id' export { default as MODULES_REGEX } from './modules-regex' diff --git a/yarn.lock b/yarn.lock index 70ce861..c2b7269 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1519,6 +1519,7 @@ __metadata: trash-cli: "npm:5.0.0" ts-dedent: "npm:2.2.0" typescript: "npm:5.3.3" + unist-util-is: "npm:6.0.0" vite-tsconfig-paths: "npm:4.3.1" vitest: "npm:1.3.1" yaml-eslint-parser: "npm:1.2.2" @@ -10410,7 +10411,7 @@ __metadata: languageName: node linkType: hard -"unist-util-is@npm:^6.0.0": +"unist-util-is@npm:6.0.0, unist-util-is@npm:^6.0.0": version: 6.0.0 resolution: "unist-util-is@npm:6.0.0" dependencies: