diff --git a/src/utils.ts b/src/utils.ts index b8e33fd..c4ddf30 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -442,7 +442,7 @@ export function parseTsAliases(basePath: string, paths: ts.MapLike) { for (const [pathWithAsterisk, replacements] of Object.entries(paths)) { const find = new RegExp( - `^${pathWithAsterisk.replace(regexpSymbolRE, '\\$1').replace(asteriskRE, '([^\\/]+)')}$` + `^${pathWithAsterisk.replace(regexpSymbolRE, '\\$1').replace(asteriskRE, '(.+)')}$` ) let index = 1 diff --git a/tests/transform.spec.ts b/tests/transform.spec.ts index 2ff8fa3..a33ee1f 100644 --- a/tests/transform.spec.ts +++ b/tests/transform.spec.ts @@ -2,6 +2,7 @@ import { resolve } from 'node:path' import { describe, expect, it } from 'vitest' import { hasExportDefault, normalizeGlob, transformCode } from '../src/transform' +import { parseTsAliases } from '../src/utils' import type { Alias } from 'vite' @@ -88,56 +89,131 @@ describe('transform tests', () => { { find: '$src', replacement: resolve(__dirname, '../src') } ] const filePath = resolve(__dirname, '../src/index.ts') - const options = (content: string) => ({ + + const options = ( + content: string, + optFilePath: string = filePath, + optAliases: Alias[] = aliases + ) => ({ content, - filePath, - aliases, + filePath: optFilePath, + aliases: optAliases, aliasesExclude: [], staticImport: false, clearPureImport: false, cleanVueFileName: false }) - expect(transformCode(options('import type { TestBase } from "@/src/test";')).content).toEqual( - "import { TestBase } from './test';\n" - ) + const tests: Array<{ + description: string, + content: string, + output: string, + filePath?: string, + aliases?: Alias[] + }> = [ + { + description: 'type import alias at root level', + content: 'import type { TestBase } from "@/src/test";', + output: "import { TestBase } from './test';\n" + }, + { + description: 'dynamic import inside subfolder with alias at root level', + content: 'import("@/components/test").Test;', + output: "import('../components/test').Test;" + }, + { + description: 'import inside folder with named alias at subfolder', + content: 'import type { TestBase } from "@/components/test";', + output: "import { TestBase } from '../components/test';\n" + }, + { + description: 'dynamic import inside subfolder with alias at root level', + content: 'import("@/components/test").Test;\n', + output: "import('../components/test').Test;\n" + }, + { + description: 'named alias in subfolder with default import', + content: 'import VContainer from "@components/layout/container/VContainer.vue";', + output: + "import { default as VContainer } from './components/layout/container/VContainer.vue';\n" + }, + { + description: 'alias at root level with tilde and no asterisk', + content: 'import { TestBase } from "~/test";', + output: "import { TestBase } from './test';\n" + }, + { + description: 'named alias at root with tilde and no asterisk', + content: 'import type { TestBase } from "$src/test";', + output: "import { TestBase } from './test';\n" + }, + { + description: 'type imports with alias at root level', + content: "const a: import('~/test').A<{ b: import('~/test').B }>", + output: "const a: import('./test').A<{ b: import('./test').B }>" + }, + { + description: 'function param and return type imports with alias at root level', + content: "function d(param: import('~/test').E): import('~/test').F", + output: "function d(param: import('./test').E): import('./test').F" + }, + { + description: 'import inside subfolder with alias at root level', + content: 'import { NestedBase } from "@/test/nested";', + output: "import { NestedBase } from '../test/nested';\n" + }, + { + description: 'alias at root level, file nested', + filePath: './src/child/folder/test.ts', + content: 'import { utilFunction } from "@/utils/test";', + output: "import { utilFunction } from '../../../utils/test';\n" + }, + { + description: 'alias as everything, relative import', + aliases: [{ find: /^(.+)$/, replacement: resolve(__dirname, '../src/$1') }], + content: 'import { TestBase } from "test";', + output: "import { TestBase } from './test';\n" + } + ] - expect(transformCode(options('import("@/components/test").Test;')).content).toEqual( - "import('../components/test').Test;" - ) - expect( - transformCode(options('import type { TestBase } from "@/components/test";')).content - ).toEqual("import { TestBase } from '../components/test';\n") + tests.forEach(({ description, content, filePath, aliases, output }) => { + expect(transformCode(options(content, filePath, aliases)).content, description).toEqual( + output + ) + }) + }) - expect(transformCode(options('import("@/components/test").Test;\n')).content).toEqual( - "import('../components/test').Test;\n" - ) + it('test: transformCode (integration test)', () => { + const tsPaths = { + '@/*': ['src/*'], + '@components/*': ['src/components/*'], + '@src': ['src'], + '*': ['src/utils/*'] + } - expect( - transformCode( - options('import VContainer from "@components/layout/container/VContainer.vue";') - ).content - ).toEqual( - "import { default as VContainer } from './components/layout/container/VContainer.vue';\n" - ) + const aliases = parseTsAliases(resolve(__dirname), tsPaths) - expect(transformCode(options('import type { TestBase } from "~/test";')).content).toEqual( - "import { TestBase } from './test';\n" - ) + const options = (content: string) => ({ + content, + filePath: resolve(__dirname, './src/index.ts'), + aliases, + aliasesExclude: [], + staticImport: true, + clearPureImport: true, + cleanVueFileName: true + }) - expect(transformCode(options('import type { TestBase } from "$src/test";')).content).toEqual( + expect(transformCode(options('import { TestBase } from "@/test";')).content).toEqual( "import { TestBase } from './test';\n" ) - expect( - transformCode( - options("const a: import('~/test').A<{ b: import('~/test').B }>") - ).content - ).toEqual("const a: import('./test').A<{ b: import('./test').B }>") + expect(transformCode(options('import { TestNested } from "@/nested/test";')).content).toEqual( + "import { TestNested } from './nested/test';\n" + ) - expect( - transformCode(options("function d(param: import('~/test').E): import('~/test').F")).content - ).toEqual("function d(param: import('./test').E): import('./test').F") + expect(transformCode(options('import { TestBase } from "./test";')).content).toEqual( + "import { TestBase } from './utils/test';\n" + ) }) it('test: transformCode (remove pure imports)', () => { diff --git a/tests/utils.spec.ts b/tests/utils.spec.ts index 3f53653..40c849d 100644 --- a/tests/utils.spec.ts +++ b/tests/utils.spec.ts @@ -1,6 +1,3 @@ -/* eslint-disable prefer-regex-literals */ -/* eslint-disable promise/param-names */ - import { normalize, resolve } from 'node:path' import { existsSync } from 'node:fs' import { describe, expect, it } from 'vitest' @@ -15,6 +12,7 @@ import { isRegExp, mergeObjects, normalizePath, + parseTsAliases, queryPublicPath, toCapitalCase, unwrapPromise @@ -160,6 +158,81 @@ describe('utils tests', () => { } }) + it('test: parseTsAliases', () => { + const maybeWindowsPath = (path: string) => + new RegExp('^([a-zA-Z]:)?' + path.replace('$', '\\$')) + + expect( + parseTsAliases('/tmp/fake/project/root', { + '@/*': ['./at/*'] + }) + ).toStrictEqual([ + { + find: /^@\/(.+)$/, + replacement: expect.stringMatching(maybeWindowsPath('/tmp/fake/project/root/at/$1')) + } + ]) + + expect(parseTsAliases('/tmp/fake/project/root', { '~/*': ['./tilde/*'] })).toStrictEqual([ + { + find: /^~\/(.+)$/, + replacement: expect.stringMatching(maybeWindowsPath('/tmp/fake/project/root/tilde/$1')) + } + ]) + + expect( + parseTsAliases('/tmp/fake/project/root', { '@/no-dot-prefix/*': ['no-dot-prefix/*'] }) + ).toStrictEqual([ + { + find: /^@\/no-dot-prefix\/(.+)$/, + replacement: expect.stringMatching( + maybeWindowsPath('/tmp/fake/project/root/no-dot-prefix/$1') + ) + } + ]) + + expect( + parseTsAliases('/tmp/fake/project/root', { '@/components/*': ['./at/components/*'] }) + ).toStrictEqual([ + { + find: /^@\/components\/(.+)$/, + replacement: expect.stringMatching( + maybeWindowsPath('/tmp/fake/project/root/at/components/$1') + ) + } + ]) + + expect(parseTsAliases('/tmp/fake/project/root', { 'top/*': ['./top/*'] })).toStrictEqual([ + { + find: /^top\/(.+)$/, + replacement: expect.stringMatching(maybeWindowsPath('/tmp/fake/project/root/top/$1')) + } + ]) + + expect(parseTsAliases('/tmp/fake/project/root', { '@src': ['./src'] })).toStrictEqual([ + { + find: /^@src$/, + replacement: expect.stringMatching(maybeWindowsPath('/tmp/fake/project/root/src')) + } + ]) + + // https://github.com/qmhc/vite-plugin-dts/issues/330 + expect(parseTsAliases('/tmp/fake/project/root', { '*': ['./src/*'] })).toStrictEqual([ + { + find: /^(.+)$/, + replacement: expect.stringMatching(maybeWindowsPath('/tmp/fake/project/root/src/$1')) + } + ]) + + // https://github.com/qmhc/vite-plugin-dts/issues/290#issuecomment-1872495764 + expect(parseTsAliases('/tmp/fake/project/root', { '#*': ['./hashed/*'] })).toStrictEqual([ + { + find: /^#(.+)$/, + replacement: expect.stringMatching(maybeWindowsPath('/tmp/fake/project/root/hashed/$1')) + } + ]) + }) + it('test: toCapitalCase', () => { expect(toCapitalCase('abc')).toEqual('Abc') expect(toCapitalCase('aa-bb-cc')).toEqual('AaBbCc') @@ -178,14 +251,8 @@ describe('utils tests', () => { const root = normalizePath(resolve(__dirname, '..')) const entryRoot = resolve(root, 'src') - expect( - getTsLibFolder({ root, entryRoot }) - ).toMatch(/node_modules\/typescript$/) + expect(getTsLibFolder({ root, entryRoot })).toMatch(/node_modules\/typescript$/) - expect( - existsSync( - getTsLibFolder({ root, entryRoot }) || '' - ) - ).toBe(true) + expect(existsSync(getTsLibFolder({ root, entryRoot }) || '')).toBe(true) }) })