Skip to content

Commit

Permalink
fix: correctly parse tsconfig paths with nested imports (#376)
Browse files Browse the repository at this point in the history
* test(transform): add failing integration test for transformCode

* fix(utils): correct issue with failing integration test

* test(utils): add tests for parseTsAliases

* test(transform): refactor process aliases test with helper and descriptions, add more cases

* test: adds test cases from issue and fixes #330

* test(utils): handle windows path
  • Loading branch information
lachieh committed Sep 1, 2024
1 parent 05a7c02 commit 10118ae
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 46 deletions.
2 changes: 1 addition & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ export function parseTsAliases(basePath: string, paths: ts.MapLike<string[]>) {

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
Expand Down
144 changes: 110 additions & 34 deletions tests/transform.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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<import('~/test').C> }>",
output: "const a: import('./test').A<{ b: import('./test').B<import('./test').C> }>"
},
{
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<import('~/test').C> }>")
).content
).toEqual("const a: import('./test').A<{ b: import('./test').B<import('./test').C> }>")
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)', () => {
Expand Down
89 changes: 78 additions & 11 deletions tests/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -15,6 +12,7 @@ import {
isRegExp,
mergeObjects,
normalizePath,
parseTsAliases,
queryPublicPath,
toCapitalCase,
unwrapPromise
Expand Down Expand Up @@ -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')
Expand All @@ -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)
})
})

0 comments on commit 10118ae

Please sign in to comment.