Skip to content

Commit 9faf6ba

Browse files
committed
feat: add support for dirs option
chore: wip chore: lint
1 parent 5e6a5cd commit 9faf6ba

File tree

4 files changed

+113
-3
lines changed

4 files changed

+113
-3
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { autoImports } from 'bun-plugin-auto-imports'
2929
const options: AutoImportsOptions = {
3030
presets: ['solid-js'], // any unimport presets are valid
3131
imports: [{ name: 'z', from: 'zod' }],
32+
dirs: ['./src'],
3233
dts: `./src/auto-import.d.ts`, // default is `./auto-import.d.ts`
3334
}
3435

src/plugin.ts

+80-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Loader, PluginBuilder } from 'bun'
22
import type { AutoImportsOptions, AutoImportsPlugin, ESLintOptions } from './types'
3+
import { Glob } from 'bun'
34
import { generateESLintGlobals } from './eslint'
45

56
function getLoader(path: string): string {
@@ -14,18 +15,94 @@ function getLoader(path: string): string {
1415

1516
export const GENERATED_COMMENT = '// Generated by bun-plugin-auto-imports\n'
1617

18+
async function scanDirExports(dir: string): Promise<string[]> {
19+
const exports: Set<string> = new Set()
20+
21+
// Create glob for TypeScript/JavaScript files
22+
const glob = new Glob('**/*.{ts,tsx,js,jsx}')
23+
24+
// Scan the directory for matching files
25+
for await (const file of glob.scan({
26+
cwd: dir,
27+
absolute: true,
28+
onlyFiles: true,
29+
followSymlinks: false,
30+
})) {
31+
// Skip definition files and node_modules
32+
if (file.includes('node_modules') || file.endsWith('.d.ts')) {
33+
continue
34+
}
35+
36+
const content = await Bun.file(file).text()
37+
38+
// Match export declarations
39+
const exportMatches = [
40+
// Named exports
41+
...content.matchAll(/export\s+(?:const|let|var|function|class|type|interface)\s+([a-zA-Z_$][\w$]*)/g),
42+
// Export statements
43+
...content.matchAll(/export\s+\{([^}]+)\}/g),
44+
// Default exports
45+
...content.matchAll(/export\s+default\s+(?:(?:function|class)\s*)?([a-zA-Z_$][\w$]*)/g),
46+
]
47+
48+
for (const match of exportMatches) {
49+
if (match[1]) {
50+
// Handle multiple exports in a single statement
51+
const names = match[1].split(',').map(name =>
52+
name.trim()
53+
.replace(/\s+as\s+(?:\S.*)?$/, '') // Remove "as" aliases
54+
.replace(/^.*(?:[\n\r\u2028\u2029]\s*|[\t\v\f \xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF])as\s+/, ''), // Handle "originalName as exportName"
55+
)
56+
names.forEach(name => exports.add(name))
57+
}
58+
}
59+
}
60+
61+
return Array.from(exports)
62+
}
63+
1764
export function autoImports(options: Partial<AutoImportsOptions>): AutoImportsPlugin {
1865
return {
1966
name: 'bun-plugin-auto-imports',
2067

2168
async setup(builder: PluginBuilder): Promise<void> {
69+
// console.log('Auto-imports plugin setup', options)
2270
const { createUnimport } = await import('unimport')
23-
const { injectImports, generateTypeDeclarations } = createUnimport({
71+
72+
// Scan directories for exports
73+
const dirExports: Record<string, string[]> = {}
74+
if (options.dirs) {
75+
for (const dir of options.dirs) {
76+
const dirPath = typeof dir === 'string' ? dir : ('path' in dir ? dir.path : dir)
77+
// console.log('Scanning directory:', dirPath)
78+
dirExports[dirPath] = await scanDirExports(dirPath)
79+
// console.log('Found exports:', dirExports[dirPath])
80+
}
81+
}
82+
83+
// Convert scanned exports to unimport format
84+
const scannedImports = Object.entries(dirExports).flatMap(([dir, exports]) =>
85+
exports.map(name => ({
86+
from: dir,
87+
name,
88+
as: name,
89+
})),
90+
)
91+
92+
const unimport = createUnimport({
2493
...options,
94+
imports: [
95+
...(options.imports || []),
96+
...scannedImports,
97+
],
2598
dts: undefined,
2699
} as AutoImportsOptions)
27100

28-
const dtsContent = await generateTypeDeclarations()
101+
const { injectImports } = unimport
102+
103+
// Generate types declarations
104+
const dtsContent = await unimport.generateTypeDeclarations()
105+
29106
// Add generated comment to d.ts file
30107
await Bun.write(
31108
options.dts ?? './auto-imports.d.ts',
@@ -40,7 +117,7 @@ export function autoImports(options: Partial<AutoImportsOptions>): AutoImportsPl
40117
}
41118

42119
const eslintContent = generateESLintGlobals(dtsContent, eslintOptions)
43-
await Bun.write(eslintOptions.filepath ?? './auto-imports.d.ts', eslintContent)
120+
await Bun.write(eslintOptions.filepath ?? './.eslint-auto-import.json', eslintContent)
44121
}
45122

46123
builder.onLoad({ filter: /.*/ }, async (args) => {

src/types.ts

+10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import type { PluginBuilder } from 'bun'
22
import type { UnimportOptions } from 'unimport'
33

4+
export interface ScanDir {
5+
path: string
6+
/**
7+
* Whether to scan for type exports
8+
* @default true
9+
*/
10+
types?: boolean
11+
}
12+
413
export interface AutoImportsPlugin {
514
name: string
615
setup: (builder: PluginBuilder) => Promise<void>
@@ -28,4 +37,5 @@ export interface ESLintOptions {
2837
export type AutoImportsOptions = Partial<UnimportOptions> & {
2938
dts?: string
3039
eslint?: ESLintOptions
40+
dirs?: (string | ScanDir)[]
3141
}

test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { AutoImportsOptions } from './src'
2+
import { plugin } from 'bun'
3+
import path from 'node:path'
4+
import { autoImports } from './src'
5+
6+
const options: AutoImportsOptions = {
7+
// presets: ['solid-js'], // any unimport presets are valid
8+
dirs: [path.resolve('./src')],
9+
dts: `./src/auto-import.d.ts`, // default is `./auto-import.d.ts`
10+
eslint: {
11+
enabled: true,
12+
},
13+
}
14+
15+
plugin(autoImports(options))
16+
17+
Bun.serve({
18+
fetch() {
19+
return new Response('Bun!')
20+
},
21+
port: 3000,
22+
})

0 commit comments

Comments
 (0)