u
This commit is contained in:
133
scripts/extractFunctions.js
Normal file
133
scripts/extractFunctions.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { parse } from 'acorn';
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const rootDir = path.resolve(scriptDir, '..');
|
||||
const outputDir = path.join(rootDir, 'output');
|
||||
const excludedDirs = new Set(['.git', 'data', 'node_modules', 'output']);
|
||||
const functions = [];
|
||||
|
||||
function collectJavaScriptFiles(dir) {
|
||||
const files = [];
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
if (entry.isDirectory() && excludedDirs.has(entry.name)) continue;
|
||||
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...collectJavaScriptFiles(fullPath));
|
||||
} else if (entry.isFile() && entry.name.endsWith('.js')) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
function inferredName(node, parent) {
|
||||
if (node.id?.name) return node.id.name;
|
||||
if (parent?.type === 'VariableDeclarator' && parent.id.type === 'Identifier') {
|
||||
return parent.id.name;
|
||||
}
|
||||
if (parent?.type === 'AssignmentExpression') {
|
||||
return parent.left.name ?? parent.left.property?.name ?? 'assigned';
|
||||
}
|
||||
if (parent?.type === 'Property' || parent?.type === 'MethodDefinition') {
|
||||
return parent.key.name ?? parent.key.value ?? 'method';
|
||||
}
|
||||
if (parent?.type === 'ExportDefaultDeclaration') return 'default';
|
||||
return 'anonymous';
|
||||
}
|
||||
|
||||
function visit(node, parent, source, relativePath) {
|
||||
if (!node || typeof node !== 'object') return;
|
||||
|
||||
if (node.type === 'FunctionDeclaration'
|
||||
|| node.type === 'FunctionExpression'
|
||||
|| node.type === 'ArrowFunctionExpression') {
|
||||
const method = (parent?.type === 'MethodDefinition'
|
||||
|| (parent?.type === 'Property' && parent.method))
|
||||
? parent
|
||||
: node;
|
||||
const start = method.start;
|
||||
const end = method.end;
|
||||
const startLine = source.slice(0, start).split('\n').length;
|
||||
const endLine = source.slice(0, end).split('\n').length;
|
||||
const name = inferredName(node, parent);
|
||||
let signature = source.slice(start, node.body.start).replace(/\s+/g, ' ').trim();
|
||||
if (method === node
|
||||
&& name !== 'anonymous'
|
||||
&& node.type !== 'FunctionDeclaration'
|
||||
&& parent?.type !== 'ExportDefaultDeclaration') {
|
||||
signature = `${name} = ${signature}`;
|
||||
}
|
||||
functions.push({
|
||||
source: relativePath,
|
||||
start,
|
||||
end,
|
||||
startLine,
|
||||
endLine,
|
||||
name,
|
||||
signature,
|
||||
code: source.slice(start, end),
|
||||
});
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(node)) {
|
||||
if (key === 'start' || key === 'end' || key === 'loc') continue;
|
||||
if (Array.isArray(value)) {
|
||||
for (const child of value) visit(child, node, source, relativePath);
|
||||
} else if (value && typeof value === 'object') {
|
||||
visit(value, node, source, relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sourceFiles = collectJavaScriptFiles(rootDir).sort();
|
||||
for (const filePath of sourceFiles) {
|
||||
const source = fs.readFileSync(filePath, 'utf8');
|
||||
const relativePath = path.relative(rootDir, filePath).split(path.sep).join('/');
|
||||
let ast;
|
||||
try {
|
||||
ast = parse(source, {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
allowHashBang: true,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`Could not parse ${relativePath}: ${error.message}`, { cause: error });
|
||||
}
|
||||
visit(ast, null, source, relativePath);
|
||||
}
|
||||
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
for (const entry of fs.readdirSync(outputDir, { withFileTypes: true })) {
|
||||
if (entry.isFile() && /^function-.*\.js$/.test(entry.name)) {
|
||||
fs.unlinkSync(path.join(outputDir, entry.name));
|
||||
}
|
||||
}
|
||||
|
||||
functions.sort((a, b) => a.source.localeCompare(b.source) || a.start - b.start);
|
||||
const nameCounts = new Map();
|
||||
for (const fn of functions) {
|
||||
const sourceSlug = fn.source.replace(/\.js$/, '').replace(/[^a-zA-Z0-9_-]+/g, '-');
|
||||
const nameSlug = fn.name.replace(/[^a-zA-Z0-9_-]+/g, '-');
|
||||
const baseName = `function-${sourceSlug}-L${fn.startLine}-${nameSlug}`;
|
||||
const sequence = (nameCounts.get(baseName) ?? 0) + 1;
|
||||
nameCounts.set(baseName, sequence);
|
||||
|
||||
const outputName = `${baseName}-${sequence}.js`;
|
||||
const contents = [
|
||||
`// Source: ${fn.source} (lines ${fn.startLine}-${fn.endLine})`,
|
||||
fn.code,
|
||||
'',
|
||||
].join('\n');
|
||||
fs.writeFileSync(path.join(outputDir, outputName), contents);
|
||||
}
|
||||
|
||||
const index = functions
|
||||
.map(fn => `${fn.source}:${fn.startLine}\t${fn.signature}`)
|
||||
.join('\n');
|
||||
fs.writeFileSync(path.join(outputDir, 'functions.txt'), `${index}\n`);
|
||||
|
||||
console.log(`Extracted ${functions.length} functions from ${sourceFiles.length} JavaScript files into ${path.relative(rootDir, outputDir)}/`);
|
||||
Reference in New Issue
Block a user