@meldscience/meld
Version:
pipeable one-shot prompt scripting toolkit
166 lines (143 loc) • 4.91 kB
text/typescript
/**
* codeImporter.ts
*
* Extracts code symbols (functions, classes, interfaces, etc.) from a TypeScript/JavaScript file.
* Supports extracting multiple symbols and adjusting the heading level and text.
*/
import { readFileSync } from 'fs';
export interface CodeImportOptions {
filePath: string;
symbolList: string[];
headingLevelOverride?: string;
headingTextOverride?: string;
skipHeading?: boolean;
}
export interface CodeImportResult {
content: string;
symbolsFound: boolean;
duplicateSymbols: boolean;
}
export function importCodeSymbols(options: CodeImportOptions): CodeImportResult {
const { filePath, symbolList, headingLevelOverride, headingTextOverride } = options;
try {
const content = readFileSync(filePath, 'utf-8');
const lines = content.split('\n');
const result: CodeImportResult = {
content: '',
symbolsFound: false,
duplicateSymbols: false
};
// Track found symbols and their locations
const foundSymbols = new Map<string, number[]>();
let inComment = false;
let inString = false;
let stringChar = '';
// Find all symbol locations
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
// Skip empty lines
if (!line) continue;
// Handle multi-line comments
if (line.includes('/*')) inComment = true;
if (line.includes('*/')) inComment = false;
// Handle strings
if (!inComment) {
for (let j = 0; j < line.length; j++) {
const char = line[j];
if (inString) {
if (char === stringChar && line[j - 1] !== '\\') {
inString = false;
}
} else if (char === '"' || char === "'") {
inString = true;
stringChar = char;
}
}
}
// Skip comments and strings
if (inComment || inString) continue;
// Look for symbol declarations
for (const symbol of symbolList) {
if (line.includes(symbol)) {
const locations = foundSymbols.get(symbol) || [];
locations.push(i);
foundSymbols.set(symbol, locations);
}
}
}
// Check for duplicates and missing symbols
result.symbolsFound = symbolList.every(symbol => foundSymbols.has(symbol));
result.duplicateSymbols = Array.from(foundSymbols.values()).some(locations => locations.length > 1);
if (!result.symbolsFound || result.duplicateSymbols) {
return result;
}
// Extract symbols
const extractedContent: string[] = [];
// Add heading by default unless skipHeading is true
if (!options.skipHeading) {
const heading = headingLevelOverride ?? '#';
const text = headingTextOverride ?? 'Code Symbols';
extractedContent.push(`${heading} ${text}`);
extractedContent.push('');
}
// Add code fence
extractedContent.push('```typescript');
// Add each symbol's code
for (const symbol of symbolList) {
const locations = foundSymbols.get(symbol)!;
const location = locations[0];
let start = location;
let end = location;
// Find the start of the symbol (include JSDoc comments)
while (start > 0 && (lines[start - 1].trim().startsWith('*') || lines[start - 1].trim().startsWith('/'))) {
start--;
}
// Find the end of the symbol (include the entire block)
let braceCount = 0;
let foundFirstBrace = false;
for (let i = location; i < lines.length; i++) {
const line = lines[i];
for (const char of line) {
if (char === '{') {
foundFirstBrace = true;
braceCount++;
} else if (char === '}') {
braceCount--;
}
}
end = i;
if (foundFirstBrace && braceCount === 0) break;
if (!foundFirstBrace && (line.trim().endsWith(';') || line.trim().endsWith('}'))) {
break;
}
}
// Add the symbol's code
for (let i = start; i <= end; i++) {
const line = lines[i];
if (i === end && symbol === symbolList[symbolList.length - 1]) {
const trimmedLine = line.trimEnd();
if (trimmedLine.endsWith(';') || trimmedLine.endsWith('}')) {
extractedContent.push(trimmedLine);
extractedContent.push('```');
} else {
extractedContent.push(trimmedLine);
extractedContent.push('```');
}
} else {
extractedContent.push(line);
}
}
// Add newline between symbols
if (symbol !== symbolList[symbolList.length - 1]) {
extractedContent.push('');
}
}
result.content = extractedContent.join('\n');
return result;
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
throw new Error('File not found');
}
throw error;
}
}