Skip to content

Commit

Permalink
chore: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbbreuer committed Oct 21, 2024
1 parent 359bea6 commit d45a5de
Showing 1 changed file with 154 additions and 172 deletions.
326 changes: 154 additions & 172 deletions src/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

0 comments on commit d45a5de

Please sign in to comment.