From d45a5dee76b1e4546c6b2156b2500a5e86230f08 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 21 Oct 2024 15:08:14 +0200 Subject: [PATCH] chore: wip --- src/extract.ts | 326 +++++++++++++++++++++++-------------------------- 1 file changed, 154 insertions(+), 172 deletions(-) diff --git a/src/extract.ts b/src/extract.ts index 053fde1..dfa0a1c 100644 --- a/src/extract.ts +++ b/src/extract.ts @@ -21,216 +21,198 @@ export function generateDtsTypes(sourceCode: string): string { let bracketCount = 0 let lastCommentBlock = '' - function processDeclaration(declaration: string): string { - console.log('processDeclaration input:', declaration) - // Remove comments - const declWithoutComments = declaration.replace(/\/\/.*$/gm, '').trim() - const trimmed = declWithoutComments - - // Handle imports - if (trimmed.startsWith('import')) { - imports.push(trimmed.endsWith(';') ? trimmed : `${trimmed};`) - return '' - } + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim() - // Handle exports from other files - if (trimmed.startsWith('export') && trimmed.includes('from')) { - exports.push(trimmed.endsWith(';') ? trimmed : `${trimmed};`) - return '' + // Handle comments + if (line.startsWith('/**') || line.startsWith('*') || line.startsWith('*/')) { + if (line.startsWith('/**')) + lastCommentBlock = '' + lastCommentBlock += `${lines[i]}\n` + continue } - // Handle const declarations - if (trimmed.startsWith('export const')) { - const equalIndex = trimmed.indexOf('=') - if (equalIndex === -1) - return trimmed // No value assigned - - const name = trimmed.slice(0, equalIndex).trim() - let value = trimmed.slice(equalIndex + 1).trim() - - // Handle multi-line object literals - if (value.startsWith('{')) { - let bracketCount = 1 - let i = 1 - while (bracketCount > 0 && i < value.length) { - if (value[i] === '{') - bracketCount++ - if (value[i] === '}') - bracketCount-- - i++ - } - value = value.slice(0, i) - } - - const declaredType = name.includes(':') ? name.split(':')[1].trim() : null + if (isMultiLineDeclaration || line.startsWith('export const') || line.startsWith('export function')) { + currentDeclaration += ` ${line}` + bracketCount += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length - if (value) { - // If we have a value, use it to infer the most specific type - if (value.startsWith('{')) { - // For object literals, preserve the exact structure - const objectType = parseObjectLiteral(value) - return `export declare const ${name.split(':')[0].replace('export const', '').trim()}: ${objectType};` - } - else { - // For primitive values, use the exact value as the type - const valueType = preserveValueType(value) - return `export declare const ${name.split(':')[0].replace('export const', '').trim()}: ${valueType};` + if (bracketCount === 0 || (i === lines.length - 1 && !line.endsWith(','))) { + if (lastCommentBlock) { + dtsLines.push(lastCommentBlock.trimEnd()) + lastCommentBlock = '' } - } - else if (declaredType) { - // If no value but a declared type, use the declared type - return `export declare const ${name.split(':')[0].replace('export const', '').trim()}: ${declaredType};` + const processed = processDeclaration(currentDeclaration.trim()) + if (processed) + dtsLines.push(processed) + isMultiLineDeclaration = false + currentDeclaration = '' } else { - // If no value and no declared type, default to 'any' - return `export declare const ${name.split(':')[0].replace('export const', '').trim()}: any;` + isMultiLineDeclaration = true } } - - // Handle other declarations (interfaces, types, functions) - if (trimmed.startsWith('export')) { - return trimmed.endsWith(';') ? trimmed : `${trimmed};` + else if (line.startsWith('export') || line.startsWith('import') || line.startsWith('interface')) { + if (lastCommentBlock) { + dtsLines.push(lastCommentBlock.trimEnd()) + lastCommentBlock = '' + } + const processed = processDeclaration(line) + if (processed) + dtsLines.push(processed) } - - return '' } - function parseObjectLiteral(objectLiteral: string): string { - console.log('parseObjectLiteral input:', objectLiteral) - // Remove the opening and closing braces - const content = objectLiteral.slice(1, -1).trim() - console.log('Cleaned content:', content) - - const pairs = [] - let currentPair = '' - let inQuotes = false - let bracketCount = 0 - let escapeNext = false - - for (const char of content) { - if (!escapeNext && (char === '"' || char === '\'')) { - inQuotes = !inQuotes - } - else if (!inQuotes) { - if (char === '{') + // Combine imports, declarations, and exports + const result = [ + ...imports, + '', + ...dtsLines, + '', + ...exports, + ].filter(Boolean).join('\n') + + return result +} + +function processDeclaration(declaration: string): string { + console.log('Processing declaration:', declaration) + // Remove comments + const declWithoutComments = declaration.replace(/\/\/.*$/gm, '').trim() + const trimmed = declWithoutComments + + // Handle const declarations + if (trimmed.startsWith('export const')) { + const equalIndex = trimmed.indexOf('=') + if (equalIndex === -1) + return trimmed // No value assigned + + const name = trimmed.slice(0, equalIndex).trim() + let value = trimmed.slice(equalIndex + 1).trim() + + console.log('Const name:', name) + console.log('Const value:', value) + + // Handle multi-line object literals + if (value.startsWith('{')) { + let bracketCount = 1 + let i = 1 + while (bracketCount > 0 && i < value.length) { + if (value[i] === '{') bracketCount++ - if (char === '}') + if (value[i] === '}') bracketCount-- + i++ } + value = value.slice(0, i) + } - if (char === ',' && !inQuotes && bracketCount === 0) { - pairs.push(currentPair.trim()) - currentPair = '' + console.log('Processed value:', value) + + const declaredType = name.includes(':') ? name.split(':')[1].trim() : null + + if (value) { + // If we have a value, use it to infer the most specific type + if (value.startsWith('{')) { + // For object literals, preserve the exact structure + const objectType = parseObjectLiteral(value) + return `export declare const ${name.split(':')[0].replace('export const', '').trim()}: ${objectType};` } else { - currentPair += char + // For primitive values, use the exact value as the type + const valueType = preserveValueType(value) + return `export declare const ${name.split(':')[0].replace('export const', '').trim()}: ${valueType};` } - escapeNext = char === '\\' && !escapeNext } - - if (currentPair.trim()) { - pairs.push(currentPair.trim()) + else if (declaredType) { + // If no value but a declared type, use the declared type + return `export declare const ${name.split(':')[0].replace('export const', '').trim()}: ${declaredType};` } + else { + // If no value and no declared type, default to 'any' + return `export declare const ${name.split(':')[0].replace('export const', '').trim()}: any;` + } + } - console.log('Pairs:', pairs) - - const parsedProperties = pairs.map((pair) => { - console.log('Processing pair:', pair) - const colonIndex = pair.indexOf(':') - if (colonIndex === -1) - return null // Invalid pair + // Handle other declarations (interfaces, types, functions) + if (trimmed.startsWith('export')) { + return trimmed.endsWith(';') ? trimmed : `${trimmed};` + } - const key = pair.slice(0, colonIndex).trim() - const value = pair.slice(colonIndex + 1).trim() - console.log('Key:', key, 'Value:', value) + return '' +} - const sanitizedValue = preserveValueType(value) - return ` ${key}: ${sanitizedValue};` - }).filter(Boolean) +function parseObjectLiteral(objectLiteral: string): string { + console.log('Parsing object literal:', objectLiteral) + // Remove the opening and closing braces + const content = objectLiteral.slice(1, -1).trim() - const result = `{\n${parsedProperties.join('\n')}\n}` - console.log('parseObjectLiteral output:', result) - return result - } + const pairs = [] + let currentPair = '' + let inQuotes = false + let bracketCount = 0 - function preserveValueType(value: string): string { - console.log('preserveValueType input:', value) - value = value.trim() - let result - if (value.startsWith('\'') || value.startsWith('"')) { - // Handle string literals, including URLs - result = value // Keep the original string as is - } - else if (value === 'true' || value === 'false') { - result = value // Keep true and false as literal types + for (const char of content) { + if (char === '"' || char === '\'') { + inQuotes = !inQuotes } - else if (!Number.isNaN(Number(value))) { - result = value // Keep numbers as literal types + else if (!inQuotes) { + if (char === '{') + bracketCount++ + if (char === '}') + bracketCount-- } - else if (value.startsWith('[') && value.endsWith(']')) { - result = 'any[]' // Generic array type + + if (char === ',' && !inQuotes && bracketCount === 0) { + pairs.push(currentPair.trim()) + currentPair = '' } else { - result = 'string' // Default to string for other cases + currentPair += char } - console.log('preserveValueType output:', result) - return result } - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim() + if (currentPair.trim()) { + pairs.push(currentPair.trim()) + } - // Handle comments - if (line.startsWith('/**') || line.startsWith('*') || line.startsWith('*/')) { - if (line.startsWith('/**')) - lastCommentBlock = '' - lastCommentBlock += `${lines[i]}\n` - continue - } + console.log('Parsed pairs:', pairs) - if (isMultiLineDeclaration) { - currentDeclaration += ` ${line}` - bracketCount += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length + const parsedProperties = pairs.map((pair) => { + const colonIndex = pair.indexOf(':') + if (colonIndex === -1) + return null // Invalid pair - if (bracketCount === 0 || i === lines.length - 1) { - if (lastCommentBlock) { - dtsLines.push(lastCommentBlock.trimEnd()) - lastCommentBlock = '' - } - const processed = processDeclaration(currentDeclaration) - if (processed) - dtsLines.push(processed) - isMultiLineDeclaration = false - currentDeclaration = '' - } - } - else if (line.startsWith('export') || line.startsWith('import') || line.startsWith('interface')) { - bracketCount = (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length + const key = pair.slice(0, colonIndex).trim() + const value = pair.slice(colonIndex + 1).trim() - if (bracketCount === 0) { - if (lastCommentBlock) { - dtsLines.push(lastCommentBlock.trimEnd()) - lastCommentBlock = '' - } - const processed = processDeclaration(line) - if (processed) - dtsLines.push(processed) - } - else { - isMultiLineDeclaration = true - currentDeclaration = line - } - } - } + console.log('Parsing pair - Key:', key, 'Value:', value) - // Combine imports, declarations, and exports - const result = [ - ...imports, - '', - ...dtsLines, - '', - ...exports, - ].filter(Boolean).join('\n') + const sanitizedValue = preserveValueType(value) + return ` ${key}: ${sanitizedValue};` + }).filter(Boolean) + const result = `{\n${parsedProperties.join('\n')}\n}` + console.log('Parsed object literal result:', result) return result } + +function preserveValueType(value: string): string { + console.log('Preserving value type for:', value) + value = value.trim() + if (value.startsWith('\'') || value.startsWith('"')) { + // Handle string literals, including URLs + return value // Keep the original string as is + } + else if (value === 'true' || value === 'false') { + return value // Keep true and false as literal types + } + else if (!Number.isNaN(Number(value))) { + return value // Keep numbers as literal types + } + else if (value.startsWith('[') && value.endsWith(']')) { + return 'any[]' // Generic array type + } + else { + return 'string' // Default to string for other cases + } +}