From 8802abde195863def869c3608b92da9e3497b10b Mon Sep 17 00:00:00 2001 From: Lexus Drumgold Date: Sun, 4 Sep 2022 17:36:31 -0400 Subject: [PATCH] feat(utils): `resolveAliases` Signed-off-by: Lexus Drumgold --- .../resolve-aliases.functional.spec.ts | 80 +++++++++++++++++++ src/utils/resolve-aliases.ts | 73 +++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 src/utils/__tests__/resolve-aliases.functional.spec.ts create mode 100644 src/utils/resolve-aliases.ts diff --git a/src/utils/__tests__/resolve-aliases.functional.spec.ts b/src/utils/__tests__/resolve-aliases.functional.spec.ts new file mode 100644 index 00000000..a3283062 --- /dev/null +++ b/src/utils/__tests__/resolve-aliases.functional.spec.ts @@ -0,0 +1,80 @@ +/** + * @file Functional Tests - resolveAliases + * @module mkbuild/utils/tests/functional/resolveAliases + */ + +import path from 'node:path' +import type { OutputFile, Statement } from 'src/interfaces' +import { createMatchPath, loadConfig } from 'tsconfig-paths' +import resolveAlias from '../resolve-alias' +import testSubject from '../resolve-aliases' + +vi.mock('pathe') + +vi.mock('tsconfig-paths', async () => { + type Actual = typeof import('tsconfig-paths') + const path: string = 'tsconfig-paths' + + const { createMatchPath, loadConfig } = await vi.importActual(path) + + return { + createMatchPath: vi.fn(createMatchPath), + loadConfig: vi.fn(loadConfig) + } +}) + +vi.mock('../resolve-alias', async () => { + type Actual = typeof import('../resolve-alias') + const path: string = '../resolve-alias' + + const { default: resolveAlias } = await vi.importActual(path) + + return { default: vi.fn(resolveAlias) } +}) + +describe('functional:utils/resolveAliases', () => { + describe('noop', () => { + it('should do nothing if output content is undefined', async () => { + // Act + await testSubject({} as OutputFile) + + // Expect + expect(loadConfig).toHaveBeenCalledTimes(0) + expect(createMatchPath).toHaveBeenCalledTimes(0) + expect(resolveAlias).toHaveBeenCalledTimes(0) + }) + + it('should do nothing if statements is empty array', async () => { + // Act + await testSubject({ contents: 'contents' } as OutputFile) + + // Expect + expect(loadConfig).toHaveBeenCalledTimes(0) + expect(createMatchPath).toHaveBeenCalledTimes(0) + expect(resolveAlias).toHaveBeenCalledTimes(0) + }) + }) + + describe('resolve', () => { + it('should resolve path aliases in output content', async () => { + const statements: Pick[] = [ + { + code: "import { MODULE_EXTENSIONS } from 'src/config/constants'", + specifier: 'src/config/constants' + } + ] + const output: Pick = { + contents: statements[0]!.code, + src: path.resolve('utils/resolve-alias.ts') + } + + // Act + await testSubject(output, statements) + + // Expect + expect(loadConfig).toHaveBeenCalledTimes(1) + expect(createMatchPath).toHaveBeenCalledTimes(1) + expect(resolveAlias).toHaveBeenCalledTimes(statements.length) + }) + }) +}) diff --git a/src/utils/resolve-aliases.ts b/src/utils/resolve-aliases.ts new file mode 100644 index 00000000..8c36d062 --- /dev/null +++ b/src/utils/resolve-aliases.ts @@ -0,0 +1,73 @@ +/** + * @file Utilities - resolveAliases + * @module mkbuild/utils/resolveAliases + */ + +import type { BuildOptions, OutputFile, Statement } from 'src/interfaces' +import type { ConfigLoaderResult, MatchPath } from 'tsconfig-paths' +import resolveAlias from './resolve-alias' + +/** + * Resolves path aliases in `output.contents`. + * + * @see https://github.com/dividab/tsconfig-paths + * + * @async + * + * @param {Pick} output - Output file object + * @param {string | undefined} [output.contents] - Output file content + * @param {string} output.src - Full path to source file + * @param {Pick[]} [statements=[]] - `import`, + * `require`, and `export` statements in `output.contents` + * @param {BuildOptions['cwd']} [cwd=process.cwd()] - Root project directory + * @return {Promise} Nothing when complete + */ +const resolveAliases = async ( + output: Pick, + statements: Pick[] = [], + cwd: BuildOptions['cwd'] = process.cwd() +): Promise => { + if (!output.contents || statements.length === 0) return output.contents + + const { createMatchPath, loadConfig } = await import('tsconfig-paths') + + /** + * `tsconfig-paths` config loader result. + * + * @const {ConfigLoaderResult} tsconfig + */ + const tsconfig: ConfigLoaderResult = loadConfig(cwd) + + if (tsconfig.resultType === 'success') { + /** + * Matches a path alias. + * + * @const {MatchPath} matcher + */ + const matcher: MatchPath = createMatchPath( + tsconfig.absoluteBaseUrl, + tsconfig.paths, + tsconfig.mainFields, + tsconfig.addMatchAll + ) + + for (const statement of statements) { + /** + * {@link statement.code} before path alias replacement. + * + * @const {string} code + */ + const code: string = statement.code + + // resolve path alias + resolveAlias(statement, output.src, matcher) + + // replace code in output content + output.contents = output.contents.replace(code, statement.code) + } + } + + return output.contents +} + +export default resolveAliases