Skip to content

Commit 1e3996e

Browse files
committed
chore: wip
1 parent 16f1500 commit 1e3996e

File tree

10 files changed

+193
-372
lines changed

10 files changed

+193
-372
lines changed

src/processor.ts

Lines changed: 109 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,35 +26,36 @@ export function processDeclarations(
2626
const defaultExport: string[] = []
2727

2828
for (const decl of exports) {
29-
const lines = decl.text.split('\n').map(line => line.trim()).filter(line => line)
30-
31-
for (const line of lines) {
32-
if (line.startsWith('export default')) {
33-
defaultExport.push(line.endsWith(';') ? line : line + ';')
34-
} else if (line.startsWith('export type {') || line.startsWith('export {')) {
35-
// Extract exported items from the line
36-
const match = line.match(/export\s+(?:type\s+)?\{\s*([^}]+)\s*\}/)
37-
if (match) {
38-
const items = match[1].split(',').map(item => item.trim())
39-
for (const item of items) {
40-
exportedItems.add(item)
41-
}
42-
}
43-
const statement = line.endsWith(';') ? line : line + ';'
44-
if (!exportStatements.includes(statement)) {
45-
exportStatements.push(statement)
46-
}
47-
} else if (line.startsWith('export ')) {
48-
const statement = line.endsWith(';') ? line : line + ';'
49-
if (!exportStatements.includes(statement)) {
50-
exportStatements.push(statement)
29+
if (decl.text.startsWith('export default')) {
30+
const statement = decl.text.endsWith(';') ? decl.text : decl.text + ';'
31+
defaultExport.push(statement)
32+
} else {
33+
// Handle multi-line export statements properly
34+
let exportText = decl.text.trim()
35+
36+
// Clean up the export text and ensure it ends with semicolon
37+
if (!exportText.endsWith(';')) {
38+
exportText += ';'
39+
}
40+
41+
// Extract exported items for tracking
42+
const match = exportText.match(/export\s+(?:type\s+)?\{\s*([^}]+)\s*\}/)
43+
if (match) {
44+
const items = match[1].split(',').map(item => item.trim())
45+
for (const item of items) {
46+
exportedItems.add(item)
5147
}
5248
}
49+
50+
if (!exportStatements.includes(exportText)) {
51+
exportStatements.push(exportText)
52+
}
5353
}
5454
}
5555

5656
// Filter imports to only include those that are used in exports or declarations
5757
const usedImports = new Set<string>()
58+
const usedImportItems = new Set<string>()
5859

5960
// Check which imports are needed based on exported functions and types
6061
for (const func of functions) {
@@ -67,23 +68,59 @@ export function processDeclarations(
6768
const importedItems = importMatch[1].split(',').map(item => item.trim())
6869
for (const item of importedItems) {
6970
if (funcDeclaration.includes(item)) {
70-
usedImports.add(imp.text)
71+
usedImportItems.add(item)
7172
}
7273
}
7374
}
7475
}
7576
}
7677
}
7778

78-
// Check which imports are needed for interfaces and types (check all, not just exported)
79+
// Check which imports are needed for exported variables
80+
for (const variable of variables) {
81+
if (variable.isExported) {
82+
for (const imp of imports) {
83+
// Handle mixed imports like: import { collect, type Collection } from 'module'
84+
const importText = imp.text
85+
const typeMatches = importText.match(/type\s+([A-Za-z_$][A-Za-z0-9_$]*)/g)
86+
const valueMatches = importText.match(/import\s+\{([^}]+)\}/)
87+
88+
// Check type imports
89+
if (typeMatches) {
90+
for (const typeMatch of typeMatches) {
91+
const typeName = typeMatch.replace('type ', '').trim()
92+
if (variable.text.includes(typeName)) {
93+
usedImportItems.add(typeName)
94+
}
95+
}
96+
}
97+
98+
// Check value imports
99+
if (valueMatches) {
100+
const imports = valueMatches[1].split(',').map(item =>
101+
item.replace(/type\s+/, '').trim()
102+
)
103+
for (const importName of imports) {
104+
if (variable.text.includes(importName)) {
105+
usedImportItems.add(importName)
106+
}
107+
}
108+
}
109+
}
110+
}
111+
}
112+
113+
// Check which imports are needed for interfaces and types (only exported ones)
79114
for (const iface of interfaces) {
80-
for (const imp of imports) {
81-
const importMatch = imp.text.match(/import\s+(?:type\s+)?\{?\s*([^}]+)\s*\}?\s+from/)
82-
if (importMatch) {
83-
const importedItems = importMatch[1].split(',').map(item => item.trim())
84-
for (const item of importedItems) {
85-
if (iface.text.includes(item)) {
86-
usedImports.add(imp.text)
115+
if (iface.isExported) {
116+
for (const imp of imports) {
117+
const importMatch = imp.text.match(/import\s+(?:type\s+)?\{?\s*([^}]+)\s*\}?\s+from/)
118+
if (importMatch) {
119+
const importedItems = importMatch[1].split(',').map(item => item.trim())
120+
for (const item of importedItems) {
121+
if (iface.text.includes(item)) {
122+
usedImportItems.add(item)
123+
}
87124
}
88125
}
89126
}
@@ -98,7 +135,7 @@ export function processDeclarations(
98135
const importedItems = importMatch[1].split(',').map(item => item.trim())
99136
for (const item of importedItems) {
100137
if (type.text.includes(item)) {
101-
usedImports.add(imp.text)
138+
usedImportItems.add(item)
102139
}
103140
}
104141
}
@@ -110,30 +147,58 @@ export function processDeclarations(
110147
for (const item of exportedItems) {
111148
for (const imp of imports) {
112149
if (imp.text.includes(item)) {
113-
usedImports.add(imp.text)
150+
// Extract the specific items from this import
151+
const importMatch = imp.text.match(/import\s+(?:type\s+)?\{?\s*([^}]+)\s*\}?\s+from/)
152+
if (importMatch) {
153+
const importedItems = importMatch[1].split(',').map(item => item.trim())
154+
for (const importedItem of importedItems) {
155+
if (item === importedItem) {
156+
usedImportItems.add(importedItem)
157+
}
158+
}
159+
}
114160
}
115161
}
116162
}
117163

118164
// Also check for value imports that are re-exported
119165
for (const exp of exports) {
120-
if (exp.text.includes('export { generate }')) {
121-
// Find the import for generate
122-
for (const imp of imports) {
123-
if (imp.text.includes('{ generate }')) {
124-
usedImports.add(imp.text)
166+
for (const imp of imports) {
167+
const importMatch = imp.text.match(/import\s+(?:type\s+)?\{?\s*([^}]+)\s*\}?\s+from/)
168+
if (importMatch) {
169+
const importedItems = importMatch[1].split(',').map(item => item.trim())
170+
for (const importedItem of importedItems) {
171+
if (exp.text.includes(importedItem)) {
172+
usedImportItems.add(importedItem)
173+
}
125174
}
126175
}
127176
}
128177
}
129178

130-
// Process and add used imports first
179+
// Create filtered imports based on actually used items
131180
const processedImports: string[] = []
132181
for (const imp of imports) {
133-
if (usedImports.has(imp.text)) {
134-
const processed = processImportDeclaration(imp)
135-
if (processed && processed.trim()) {
136-
processedImports.push(processed)
182+
const importMatch = imp.text.match(/import\s+(?:type\s+)?\{?\s*([^}]+)\s*\}?\s+from\s+['"]([^'"]+)['"]/)
183+
if (importMatch) {
184+
const importedItems = importMatch[1].split(',').map(item => item.trim())
185+
const usedItems = importedItems.filter(item => {
186+
const cleanItem = item.replace(/^type\s+/, '').trim()
187+
return usedImportItems.has(cleanItem)
188+
})
189+
190+
if (usedItems.length > 0) {
191+
const source = importMatch[2]
192+
const hasTypeImports = usedItems.some(item => item.startsWith('type '))
193+
const hasValueImports = usedItems.some(item => !item.startsWith('type '))
194+
195+
let importStatement = 'import '
196+
if (hasTypeImports && !hasValueImports) {
197+
importStatement += 'type '
198+
}
199+
importStatement += `{ ${usedItems.join(', ')} } from '${source}';`
200+
201+
processedImports.push(importStatement)
137202
}
138203
}
139204
}
@@ -983,4 +1048,4 @@ function inferFunctionType(value: string, inUnion: boolean = false): string {
9831048

9841049
const funcType = '() => unknown'
9851050
return inUnion ? `(${funcType})` : funcType
986-
}
1051+
}

test/fixtures/input/example/0010.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface FormatPhoneOptions {
1212

1313
export const DefaultPhoneRegion = 'US'
1414

15-
const PHONE_PATTERNS: Record<string, number[]> = {
15+
export const PHONE_PATTERNS: Record<string, number[]> = {
1616
US: [3, 3, 4],
1717
GB: [4, 3, 3],
1818
FR: [2, 2, 2, 2, 2],
@@ -25,7 +25,7 @@ const PHONE_PATTERNS: Record<string, number[]> = {
2525
CA: [3, 3, 4],
2626
} as const
2727

28-
const COUNTRY_CODES: Record<string, string> = {
28+
export const COUNTRY_CODES: Record<string, string> = {
2929
US: '+1',
3030
GB: '+44',
3131
FR: '+33',

test/fixtures/output/example/0004.d.ts

Lines changed: 46 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,52 +8,46 @@ export declare interface DtsGenerationConfig {
88
tsconfigPath: string
99
verbose: boolean
1010
}
11-
export declare type DtsGenerationOption = Partial<DtsGenerationConfig>
12-
13-
export type DtsGenerationOptions = DtsGenerationOption | DtsGenerationOption[]
14-
15-
export interface RegexPatterns {
16-
readonly typeImport: RegExp
17-
readonly regularImport: RegExp
18-
readonly bracketOpen: RegExp
19-
readonly bracketClose: RegExp
20-
readonly functionReturn: RegExp
21-
readonly typeAnnotation: RegExp
22-
readonly asyncFunction: RegExp
23-
readonly genericParams: RegExp
24-
readonly functionParams: RegExp
25-
readonly functionReturnType: RegExp
26-
readonly destructuredParams: RegExp
27-
readonly typePattern: RegExp
28-
readonly valueReference: RegExp
29-
readonly typeReference: RegExp
30-
readonly functionName: RegExp
31-
readonly exportCleanup: RegExp
32-
readonly defaultExport: RegExp
33-
readonly complexType: RegExp
34-
readonly unionIntersection: RegExp
35-
readonly mappedType: RegExp
36-
readonly conditionalType: RegExp
37-
readonly genericConstraints: RegExp
38-
readonly functionOverload: RegExp
39-
readonly moduleDeclaration: RegExp
40-
readonly moduleAugmentation: RegExp
11+
export declare interface RegexPatterns {
12+
typeImport: RegExp
13+
regularImport: RegExp
14+
bracketOpen: RegExp
15+
bracketClose: RegExp
16+
functionReturn: RegExp
17+
typeAnnotation: RegExp
18+
asyncFunction: RegExp
19+
genericParams: RegExp
20+
functionParams: RegExp
21+
functionReturnType: RegExp
22+
destructuredParams: RegExp
23+
typePattern: RegExp
24+
valueReference: RegExp
25+
typeReference: RegExp
26+
functionName: RegExp
27+
exportCleanup: RegExp
28+
defaultExport: RegExp
29+
complexType: RegExp
30+
unionIntersection: RegExp
31+
mappedType: RegExp
32+
conditionalType: RegExp
33+
genericConstraints: RegExp
34+
functionOverload: RegExp
35+
moduleDeclaration: RegExp
36+
moduleAugmentation: RegExp
4137
}
42-
43-
export interface ImportTrackingState {
44-
typeImports: Map<string, Set<string>>
45-
valueImports: Map<string, Set<string>>
46-
usedTypes: Set<string>
47-
usedValues: Set<string>
48-
exportedTypes: Set<string>
49-
exportedValues: Set<string>
50-
valueAliases: Map<string, string>
51-
importSources: Map<string, string>
52-
typeExportSources: Map<string, string>
53-
defaultExportValue?: string
38+
export declare interface ImportTrackingState {
39+
typeImports: Map<string, Set<string>>
40+
valueImports: Map<string, Set<string>>
41+
usedTypes: Set<string>
42+
usedValues: Set<string>
43+
exportedTypes: Set<string>
44+
exportedValues: Set<string>
45+
valueAliases: Map<string, string>
46+
importSources: Map<string, string>
47+
typeExportSources: Map<string, string>
48+
defaultExportValue?: string
5449
}
55-
56-
export interface ProcessingState {
50+
export declare interface ProcessingState {
5751
dtsLines: string[]
5852
imports: string[]
5953
usedTypes: Set<string>
@@ -78,38 +72,35 @@ export interface ProcessingState {
7872
defaultExports: Set<string>
7973
currentScope: 'top' | 'function'
8074
}
81-
82-
export interface MethodSignature {
75+
export declare interface MethodSignature {
8376
name: string
8477
async: boolean
8578
generics: string
8679
params: string
8780
returnType: string
8881
}
89-
90-
export interface PropertyInfo {
82+
export declare interface PropertyInfo {
9183
key: string
9284
value: string
9385
type: string
9486
nested?: PropertyInfo[]
9587
method?: MethodSignature
9688
}
97-
98-
export interface ImportInfo {
89+
export declare interface ImportInfo {
9990
kind: 'type' | 'value' | 'mixed'
10091
usedTypes: Set<string>
10192
usedValues: Set<string>
10293
source: string
10394
}
104-
105-
export interface FunctionSignature {
95+
export declare interface FunctionSignature {
10696
name: string
10797
params: string
10898
returnType: string
10999
generics: string
110100
}
111-
112-
export interface ProcessedMethod {
101+
export declare interface ProcessedMethod {
113102
name: string
114103
signature: string
115-
}
104+
}
105+
export type DtsGenerationOption = Partial<DtsGenerationConfig>
106+
export type DtsGenerationOptions = DtsGenerationOption | DtsGenerationOption[]

0 commit comments

Comments
 (0)