Skip to content

Commit 87157e0

Browse files
committed
chore: wip
chore: wip chore: wip chore: wip
1 parent 5b2042a commit 87157e0

2 files changed

Lines changed: 221 additions & 44 deletions

File tree

fixtures/output/example-0001.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export declare const someObject: {
1515
someFunction: Function;
1616
anotherOne: Function;
1717
someArray: Array<any>;
18-
someNestedArray: Array<any>;
18+
someNestedArray: Array<Array<number>>;
1919
someComplexArray: Array<any>;
2020
someObject: Object;
2121
someNestedObject: Object;

src/extract.ts

Lines changed: 220 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface PropertyInfo {
99
key: string
1010
value: string
1111
type: string
12+
nested?: PropertyInfo[]
1213
}
1314

1415
export async function extract(filePath: string): Promise<string> {
@@ -95,7 +96,8 @@ function generateDtsTypes(sourceCode: string): string {
9596
.filter(Boolean)
9697
.join('\n')
9798

98-
console.log('Final result', result)
99+
console.log('result:', result)
100+
99101
return defaultExport ? `${result}\n${defaultExport}` : result
100102
}
101103

@@ -163,79 +165,254 @@ function processConstDeclaration(declaration: string, isExported = true): string
163165
}
164166

165167
const properties = extractObjectProperties(lines.slice(1, -1))
166-
const propertyStrings = properties.map(prop => ` ${prop.key}: ${prop.type};`)
168+
const propertyStrings = formatProperties(properties)
169+
170+
return `${isExported ? 'export ' : ''}declare const ${name}: {\n${propertyStrings}\n};`
171+
}
167172

168-
return `${isExported ? 'export ' : ''}declare const ${name}: {\n${propertyStrings.join('\n')}\n};`
173+
function formatProperties(properties: PropertyInfo[], indent = 2): string {
174+
return properties.map((prop) => {
175+
const spaces = ' '.repeat(indent)
176+
if (prop.nested && prop.nested.length > 0) {
177+
const nestedProps = formatProperties(prop.nested, indent + 2)
178+
return `${spaces}${prop.key}: {\n${nestedProps}\n${spaces}};`
179+
}
180+
return `${spaces}${prop.key}: ${prop.type};`
181+
}).join('\n')
169182
}
170183

171184
function extractObjectProperties(lines: string[]): PropertyInfo[] {
172185
const properties: PropertyInfo[] = []
173-
let currentProperty: Partial<PropertyInfo> | null = null
186+
let currentBlock = ''
174187
let bracketCount = 0
188+
let braceCount = 0
175189

176190
for (const line of lines) {
177191
const trimmedLine = line.trim()
192+
178193
if (!trimmedLine || trimmedLine.startsWith('//') || trimmedLine.startsWith('/*'))
179194
continue
180195

181-
if (currentProperty === null) {
182-
const match = trimmedLine.match(/^(\w+)\s*:\s*(.+?),?$/)
183-
if (match) {
184-
const [, key, value] = match
185-
properties.push({
186-
key,
187-
value,
188-
type: inferType(value.trim()),
189-
})
190-
}
191-
}
192-
else {
193-
bracketCount += (trimmedLine.match(/\{/g) || []).length
194-
bracketCount -= (trimmedLine.match(/\}/g) || []).length
195-
196-
if (bracketCount === 0) {
197-
if (currentProperty.key) {
198-
properties.push({
199-
key: currentProperty.key,
200-
value: currentProperty.value || '',
201-
type: currentProperty.type || 'any',
202-
})
196+
// Count brackets and braces
197+
const openBrackets = (trimmedLine.match(/\[/g) || []).length
198+
const closeBrackets = (trimmedLine.match(/\]/g) || []).length
199+
const openBraces = (trimmedLine.match(/\{/g) || []).length
200+
const closeBraces = (trimmedLine.match(/\}/g) || []).length
201+
202+
bracketCount += openBrackets - closeBrackets
203+
braceCount += openBraces - closeBraces
204+
205+
currentBlock += `${trimmedLine} `
206+
207+
// Process complete property when we're back at root level
208+
if (bracketCount === 0 && braceCount === 0 && currentBlock.includes(':')) {
209+
const propertyMatch = currentBlock.match(/^(\w+)\s*:\s*(.+?)(?:,\s*$|,?\s*$)/)
210+
if (propertyMatch) {
211+
const [, key, rawValue] = propertyMatch
212+
const value = rawValue.trim()
213+
const propertyInfo = extractPropertyInfo(key, value, lines)
214+
if (propertyInfo) {
215+
properties.push(propertyInfo)
203216
}
204-
currentProperty = null
205217
}
218+
currentBlock = ''
206219
}
207220
}
208221

209222
return properties
210223
}
211224

212-
function inferType(value: string): string {
213-
// Handle string literals - keep the quotes
214-
if (value.startsWith('"') || value.startsWith('\'')) {
215-
// Ensure consistent quote style (using single quotes)
216-
const cleanValue = value.trim().replace(/^["']|["']$/g, '')
217-
return `'${cleanValue}'`
225+
function extractPropertyInfo(key: string, value: string, originalLines: string[]): PropertyInfo {
226+
// Handle multiline object definitions
227+
if (value.startsWith('{') && value.includes('{}')) {
228+
const objectLines = originalLines.filter(line =>
229+
line.trim().startsWith(key)
230+
|| (line.includes('{') && line.includes('}'))
231+
|| line.trim().endsWith(',')
232+
|| line.trim().endsWith('}'),
233+
)
234+
235+
const nestedProperties = parseNestedObject(objectLines)
236+
if (nestedProperties.length > 0) {
237+
return {
238+
key,
239+
value,
240+
type: formatNestedType(nestedProperties),
241+
nested: nestedProperties,
242+
}
243+
}
244+
}
245+
246+
// Handle arrow functions and function declarations
247+
if ((value.includes('=>') && !value.includes('['))
248+
|| value.startsWith('function')
249+
|| (value.includes('()') && value.includes('{'))) {
250+
return {
251+
key,
252+
value,
253+
type: 'Function',
254+
}
255+
}
256+
257+
// Handle arrays
258+
if (value.startsWith('[')) {
259+
return {
260+
key,
261+
value,
262+
type: inferArrayType(value),
263+
}
264+
}
265+
266+
// Handle inline objects
267+
if (value.startsWith('{')) {
268+
const objectContent = value.slice(1, -1).trim()
269+
const nestedProps = extractObjectProperties([objectContent])
270+
return {
271+
key,
272+
value,
273+
type: formatObjectType(nestedProps),
274+
nested: nestedProps.length > 0 ? nestedProps : undefined,
275+
}
276+
}
277+
278+
// Handle function references and console methods
279+
if (value === 'console.log' || value.endsWith('.log')) {
280+
return {
281+
key,
282+
value,
283+
type: 'Function',
284+
}
285+
}
286+
287+
// Handle string literals
288+
if (value.startsWith('\'') || value.startsWith('"')) {
289+
const cleanValue = value.slice(1, -1)
290+
return {
291+
key,
292+
value,
293+
type: `'${cleanValue}'`,
294+
}
295+
}
296+
297+
// Handle numbers
298+
if (!isNaN(Number(value))) {
299+
return {
300+
key,
301+
value,
302+
type: value,
303+
}
304+
}
305+
306+
// Handle booleans
307+
if (value === 'true' || value === 'false') {
308+
return {
309+
key,
310+
value,
311+
type: value,
312+
}
313+
}
314+
315+
// Handle object references
316+
return {
317+
key,
318+
value,
319+
type: 'Object',
218320
}
321+
}
322+
323+
function parseNestedObject(lines: string[]): PropertyInfo[] {
324+
const nestedLines = lines
325+
.map(line => line.trim())
326+
.filter(line => line && !line.startsWith('//'))
327+
328+
let braceCount = 0
329+
let currentBlock = ''
330+
const properties: PropertyInfo[] = []
331+
332+
for (const line of nestedLines) {
333+
braceCount += (line.match(/\{/g) || []).length
334+
braceCount -= (line.match(/\}/g) || []).length
219335

220-
if (value === 'true' || value === 'false')
221-
return value
336+
if (line.includes(':')) {
337+
const [key, ...valueParts] = line.split(':')
338+
const value = valueParts.join(':').trim()
222339

223-
if (!Number.isNaN(Number(value)))
224-
return value
340+
if (value.includes('{')) {
341+
// Start of nested object
342+
currentBlock = `${key.trim()}: ${value}`
343+
}
344+
else if (braceCount === 0) {
345+
// Simple property
346+
const propInfo = extractPropertyInfo(
347+
key.trim(),
348+
value.replace(/,$/, ''),
349+
nestedLines,
350+
)
351+
properties.push(propInfo)
352+
}
353+
}
354+
}
355+
356+
return properties
357+
}
225358

226-
if (value.includes('=>') || value.includes('function'))
227-
return 'Function'
359+
function inferArrayType(arrayValue: string): string {
360+
const content = arrayValue.slice(1, -1).trim()
228361

229-
if (value.startsWith('['))
362+
if (!content)
230363
return 'Array<any>'
231364

232-
if (value.startsWith('{'))
365+
// Handle nested arrays
366+
if (content.startsWith('[')) {
367+
const nestedContent = content.match(/\[(.*)\]/)?.[1]
368+
return `Array<${nestedContent ? inferArrayType(`[${nestedContent}]`) : 'any'}>`
369+
}
370+
371+
// Handle array of objects
372+
if (content.includes('{')) {
373+
const objects = content.split('},{').map(obj => obj.replace(/^\{|\}$/g, ''))
374+
const objectProps = objects.map(obj => extractObjectProperties([obj]))
375+
return `Array<${formatObjectType(objectProps[0])}>`
376+
}
377+
378+
// Handle simple numeric arrays
379+
if (content.split(',').every(item => !isNaN(Number(item.trim())))) {
380+
return 'Array<number>'
381+
}
382+
383+
return 'Array<any>'
384+
}
385+
386+
function formatObjectType(properties: PropertyInfo[]): string {
387+
if (properties.length === 0)
233388
return 'Object'
234389

235-
if (value.includes('.'))
390+
const formattedProps = properties
391+
.map(prop => `${prop.key}: ${prop.nested ? formatNestedType(prop.nested) : prop.type}`)
392+
.join('; ')
393+
394+
return `{ ${formattedProps} }`
395+
}
396+
397+
function isFunctionReference(value: string): boolean {
398+
// Check for common function reference patterns
399+
return value.endsWith('.log')
400+
|| value.endsWith('()')
401+
|| value.includes('.bind')
402+
|| value.includes('.call')
403+
|| value.includes('.apply')
404+
|| /\w+\.\w+/.test(value) // Matches object method references
405+
}
406+
407+
function formatNestedType(properties: PropertyInfo[]): string {
408+
if (properties.length === 0)
236409
return 'Object'
237410

238-
return value
411+
const formattedProps = properties
412+
.map(prop => `${prop.key}: ${prop.nested ? formatNestedType(prop.nested) : prop.type}`)
413+
.join(', ')
414+
415+
return `{ ${formattedProps} }`
239416
}
240417

241418
function processInterfaceDeclaration(declaration: string, isExported = true): string {

0 commit comments

Comments
 (0)