Skip to content

Commit

Permalink
feat: support Alias
Browse files Browse the repository at this point in the history
  • Loading branch information
s3xysteak committed Jul 1, 2024
1 parent 7a2a508 commit 4af6b4c
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 6 deletions.
76 changes: 76 additions & 0 deletions src/core/alias.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// TODO: Copy from [unjs/pathe](https://github.com/unjs/pathe/blob/main/src/utils.ts)
import { join, toNamespacedPath } from 'pathe'

const pathSeparators = new Set(['/', '\\', undefined])

const normalizedAliasSymbol = Symbol.for('pathe:normalizedAlias')

export function normalizeAliases(_aliases: Record<string, string>) {
if ((_aliases as any)[normalizedAliasSymbol]) {
return _aliases
}

// Sort aliases from specific to general (ie. fs/promises before fs)
const aliases = Object.fromEntries(
Object.entries(_aliases).sort(([a], [b]) => _compareAliases(a, b)),
)

// Resolve alias values in relation to each other
for (const key in aliases) {
for (const alias in aliases) {
// don't resolve a more specific alias with regard to a less specific one
if (alias === key || key.startsWith(alias)) {
continue
}

if (
aliases[key].startsWith(alias)
&& pathSeparators.has(aliases[key][alias.length])
) {
aliases[key] = aliases[alias] + aliases[key].slice(alias.length)
}
}
}

Object.defineProperty(aliases, normalizedAliasSymbol, {
value: true,
enumerable: false,
})
return aliases
}

export function resolveAlias(path: string, aliases: Record<string, string>) {
const _path = toNamespacedPath(path)
aliases = normalizeAliases(aliases)
for (const [alias, to] of Object.entries(aliases)) {
if (!_path.startsWith(alias)) {
continue
}

// Strip trailing slash from alias for check
const _alias = hasTrailingSlash(alias) ? alias.slice(0, -1) : alias

if (hasTrailingSlash(_path[_alias.length])) {
return join(to, _path.slice(alias.length))
}
}
return _path
}

const FILENAME_RE = /(^|[/\\])([^/\\]+?)(?=(\.[^.]+)?$)/

export function filename(path: string) {
return path.match(FILENAME_RE)?.[2]
}

// --- internals ---

function _compareAliases(a: string, b: string) {
return b.split('/').length - a.split('/').length
}

// Returns true if path ends with a slash or **is empty**
function hasTrailingSlash(path = '/') {
const lastChar = path[path.length - 1]
return lastChar === '/' || lastChar === '\\'
}
7 changes: 5 additions & 2 deletions src/core/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ interface ExpGeneratorDataOptions {
* @default false
*/
exportDefault: boolean

alias: Record<string, string>
}

export interface ExpGeneratorOptions extends ExpGeneratorDataOptions {
Expand All @@ -73,7 +75,7 @@ export async function expGenerator(path: string, options: Partial<ExpGeneratorOp
if (isUndefined(options?.writeTo))
return await fs.writeFile(targetPath, data)

const extension = (path: string) => /\.\w+?$/.test(path)
const extension = (path: string) => /\.\w+$/.test(path)
? path
: `${path}.${isTs ? 'ts' : 'js'}`

Expand All @@ -93,13 +95,14 @@ export async function expGeneratorData(path: string, options?: Partial<ExpGenera
rename = 'autoImport',
typescript = isTs,
exportDefault = false,
alias,
} = options ?? {}

exclude.push(rename)

const targetPath = await findPath(path, base)

const expList = await expCollector(path, base)
const expList = await expCollector(path, { base, alias })
let content = await fs.readFile(targetPath, 'utf-8')

const _firstComment = firstIndex(content)
Expand Down
15 changes: 13 additions & 2 deletions src/core/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,19 @@ import { parse as swcParse } from '@swc/core'
import { p } from '@s3xysteak/utils'

import { findPath, getPkg } from './utils'
import { resolveAlias } from './alias'

interface ExpCollectorOptions {
base?: string
alias?: Record<string, string>
}

export async function expCollector(path: string, options?: ExpCollectorOptions): Promise<string[]> {
const {
base,
alias = {},
} = options ?? {}

export async function expCollector(path: string, base?: string): Promise<string[]> {
const result = new Set<string>()

const recursion = async (path: string, base?: string) => {
Expand All @@ -18,7 +29,7 @@ export async function expCollector(path: string, base?: string): Promise<string[
exp.forEach((val) => { result.add(val) })

await p(refer, { concurrency: Number.POSITIVE_INFINITY })
.forEach(async path => await recursion(path, dirname(filePath)))
.forEach(async path => await recursion(resolveAlias(path, alias), dirname(filePath)))
}

await recursion(path, base ?? process.cwd())
Expand Down
65 changes: 64 additions & 1 deletion test/generator.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { promises as fs } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { toLF } from '@s3xysteak/utils'
import { expGenerator } from '@/core/generator'

describe('generate', () => {
describe.concurrent('generate', () => {
/** TS */
it('expGenerator should work with TS', async () => {
const exportList = ['custom', 'two', 'getThree', 'funcIndex', 'ClassIndex', 'func3', 'func2', 'func1', 'fRe'].sort()

Expand Down Expand Up @@ -56,6 +58,7 @@ export function autoImport(map?: Partial<{ [K in typeof __UnExportList[number]]:
expect(toLF(result)).toBe(`${target.trim()}\n`)
})

/** JS */
it('expGenerator should work with JS', async () => {
const exportList = ['custom', 'two', 'getThree', 'funcIndex', 'ClassIndex', 'func3', 'func2', 'func1', 'fRe'].sort()

Expand Down Expand Up @@ -109,6 +112,7 @@ export function autoImport(map) {
expect(toLF(result)).toBe(`${target.trim()}\n`)
})

/** writeTo option */
it('expGenerator should work with writeTo option', async () => {
const exportList = ['custom', 'two', 'getThree', 'funcIndex', 'ClassIndex', 'func3', 'func2', 'func1', 'fRe'].sort()

Expand Down Expand Up @@ -146,4 +150,63 @@ export function autoImport(map?: Partial<{ [K in typeof __UnExportList[number]]:

expect(toLF(result)).toBe(`${target.trim()}\n`)
})

/** aliases */
it('expGenerator should work with aliases', async () => {
const exportList = ['custom', 'two', 'getThree', 'funcIndex', 'ClassIndex', 'func3', 'func2', 'func1', 'fRe'].sort()

const target = `
export const one = 1
export const two = 2
export const getThree = () => 3
export function funcIndex() {}
export class ClassIndex {}
export type NumOrStr = number | string
export * from './core/func1'
export * from '~test/parser-lab/core/func2'
export * from '@s3xysteak/utils'
// --- Auto-Generated By Unplugin-Export-Collector ---
const __UnExportList = ${JSON.stringify(exportList)} as const
/**
* @returns Call in \`resolvers\` option of \`unplugin-auto-import\`.
*/
export function autoImport(map?: Partial<{ [K in typeof __UnExportList[number]]: string }>) {
return (name: string) => {
if (!__UnExportList.includes(name as any))
return
return map && (map as any)[name]
? {
name,
as: (map as any)[name],
from: 'unplugin-export-collector',
}
: {
name,
from: 'unplugin-export-collector',
}
}
}
// --- Auto-Generated By Unplugin-Export-Collector ---
`

await expGenerator('./test/parser-lab/generatorTestWithAlias.ts', {
include: ['custom'],
exclude: ['one'],
alias: {
'~test': fileURLToPath(new URL('.', import.meta.url)),
},
})

const result = await fs.readFile('./test/parser-lab/generatorTestWithAlias.ts', 'utf-8')

expect(toLF(result)).toBe(`${target.trim()}\n`)
})
})
39 changes: 39 additions & 0 deletions test/parser-lab/generatorTestWithAlias.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export const one = 1
export const two = 2
export const getThree = () => 3
export function funcIndex() {}
export class ClassIndex {}

export type NumOrStr = number | string

export * from './core/func1'
export * from '~test/parser-lab/core/func2'

export * from '@s3xysteak/utils'

// --- Auto-Generated By Unplugin-Export-Collector ---

const __UnExportList = ["ClassIndex","custom","fRe","func1","func2","func3","funcIndex","getThree","two"] as const

/**
* @returns Call in `resolvers` option of `unplugin-auto-import`.
*/
export function autoImport(map?: Partial<{ [K in typeof __UnExportList[number]]: string }>) {
return (name: string) => {
if (!__UnExportList.includes(name as any))
return

return map && (map as any)[name]
? {
name,
as: (map as any)[name],
from: 'unplugin-export-collector',
}
: {
name,
from: 'unplugin-export-collector',
}
}
}

// --- Auto-Generated By Unplugin-Export-Collector ---
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"paths": {
"@/*": ["src/*"],
"@core/*": ["src/core/*"],
"@utils/*": ["src/utils/*"]
"@utils/*": ["src/utils/*"],
"~test/*": ["test/*"]
},

"resolveJsonModule": true,
Expand Down

0 comments on commit 4af6b4c

Please sign in to comment.