@notjustcoders/ioc-arise
Version:
Arise type-safe IoC containers from your code. Zero overhead, zero coupling.
330 lines • 15.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ASTParser = void 0;
const fs_1 = require("fs");
const napi_1 = require("@ast-grep/napi");
const one_logger_client_sdk_1 = require("@notjustcoders/one-logger-client-sdk");
const index_js_1 = require("../errors/index.js");
class ASTParser {
parseFile(filePath) {
try {
const content = (0, fs_1.readFileSync)(filePath, 'utf-8');
const ast = napi_1.ts.parse(content);
return ast.root();
}
catch (error) {
throw index_js_1.ErrorFactory.fileReadError(filePath, error instanceof Error ? error.message : String(error));
}
}
findClassesImplementingInterfaces(root) {
return root.findAll({
rule: {
pattern: 'class $CLASS implements $INTERFACE { $$$ }'
}
});
}
findClassesExtendingClasses(root) {
return root.findAll({
rule: {
pattern: 'class $CLASS extends $PARENT { $$$ }'
}
});
}
findAllClasses(root) {
// Find regular class declarations
const regularClasses = root.findAll({
rule: {
kind: 'class_declaration'
}
});
// Find abstract class declarations using pattern matching
const abstractClasses = root.findAll({
rule: {
pattern: 'abstract class $NAME { $$$ }'
}
});
// Also try to find exported abstract classes
const exportedAbstractClasses = root.findAll({
rule: {
pattern: 'export abstract class $NAME { $$$ }'
}
});
// Combine all found classes
return [...regularClasses, ...abstractClasses, ...exportedAbstractClasses];
}
extractClassName(classNode) {
one_logger_client_sdk_1.logger.log('Extracting class name from node:', { result: classNode.text().substring(0, 100) + '...' });
// Try different approaches to extract class name
let className = classNode.getMatch('CLASS')?.text();
if (className) {
one_logger_client_sdk_1.logger.log('Found class name using CLASS match:', className);
return className;
}
// Try to match the class declaration pattern directly
const classText = classNode.text();
const classMatch = classText.match(/class\s+([A-Za-z_][A-Za-z0-9_]*)/);
if (classMatch) {
className = classMatch[1];
one_logger_client_sdk_1.logger.log('Found class name using regex:', className);
return className;
}
one_logger_client_sdk_1.logger.log('Failed to extract class name');
return undefined;
}
extractInterfaceName(classNode) {
return classNode.getMatch('INTERFACE')?.text();
}
extractParentClassName(classNode) {
return classNode.getMatch('PARENT')?.text();
}
isAbstractClass(classNode) {
const classText = classNode.text();
console.log(`DEBUG: isAbstractClass - classText: ${classText}`);
const isAbstract = classText.includes('abstract class');
console.log(`DEBUG: isAbstractClass - result: ${isAbstract}`);
return isAbstract;
}
extractConstructorParameters(classNode) {
const parameters = [];
try {
// Find constructor within the class
const constructorNodes = classNode.findAll({
rule: {
kind: 'method_definition',
pattern: "$NAME",
regex: "^constructor"
}
});
one_logger_client_sdk_1.logger.log(`Found ${constructorNodes.length} constructor nodes`);
if (constructorNodes.length === 0) {
return parameters;
}
const constructorNode = constructorNodes[0];
// Find formal parameters within the constructor
const parameterNodes = constructorNode.findAll({
rule: {
kind: 'formal_parameters'
}
});
if (parameterNodes.length === 0) {
return parameters;
}
const formalParams = parameterNodes[0];
// Find individual parameters (both required and optional)
const requiredParamNodes = formalParams.findAll({
rule: {
kind: 'required_parameter'
}
});
const optionalParamNodes = formalParams.findAll({
rule: {
kind: 'optional_parameter'
}
});
const paramNodes = [...requiredParamNodes, ...optionalParamNodes];
one_logger_client_sdk_1.logger.log(`Found ${paramNodes.length} parameter nodes`);
for (const paramNode of paramNodes) {
const paramText = paramNode.text();
one_logger_client_sdk_1.logger.log('Parameter text:', paramText);
// Parse parameter using regex to extract details
const paramMatch = paramText.match(/(?:(private|public|protected)\s+)?(\w+)(\?)?\s*:\s*(\w+)/);
if (paramMatch) {
const [, accessModifier, name, optional, type] = paramMatch;
parameters.push({
name: name.trim(),
type: type.trim(),
isOptional: !!optional,
accessModifier: accessModifier
});
one_logger_client_sdk_1.logger.log(`Extracted parameter: ${name}: ${type}${optional ? '?' : ''} (${accessModifier || 'none'})`);
}
}
}
catch (error) {
one_logger_client_sdk_1.logger.warn('Warning: Could not extract constructor parameters:', { error });
}
one_logger_client_sdk_1.logger.log('Final parameters:', { parameters });
return parameters;
}
extractJSDocComments(root) {
const classScopes = new Map();
try {
// Find all JSDoc comments in the file
const comments = root.findAll({
rule: {
kind: 'comment'
}
});
one_logger_client_sdk_1.logger.log(`Found ${comments.length} comments in file`);
// Find all class declarations
const classDeclarations = root.findAll({
rule: {
kind: 'class_declaration'
}
});
one_logger_client_sdk_1.logger.log(`Found ${classDeclarations.length} class declarations in file`);
for (const comment of comments) {
const commentText = comment.text();
one_logger_client_sdk_1.logger.log(`Comment text: ${commentText}`);
if (commentText.includes('/**') && commentText.includes('@scope')) {
one_logger_client_sdk_1.logger.log(`Found @scope comment: ${commentText}`);
const scopeMatch = commentText.match(/@scope\s+(singleton|transient)/);
if (scopeMatch) {
const scope = scopeMatch[1];
const commentRange = comment.range();
one_logger_client_sdk_1.logger.log(`Comment range: line ${commentRange.start.line} to ${commentRange.end.line}`);
// Find the class declaration that comes immediately after this comment
for (const classDecl of classDeclarations) {
const classRange = classDecl.range();
one_logger_client_sdk_1.logger.log(`Class range: line ${classRange.start.line} to ${classRange.end.line}`);
// Check if the class comes after the comment
if (classRange.start.line > commentRange.end.line) {
const className = this.extractClassName(classDecl);
one_logger_client_sdk_1.logger.log(`Attempting to extract class name from class at line ${classRange.start.line}`);
one_logger_client_sdk_1.logger.log(`Extracted class name: ${className}`);
if (className) {
one_logger_client_sdk_1.logger.log(`Found JSDoc scope annotation: ${className} -> ${scope}`);
classScopes.set(className, scope);
break; // Take the first class after this comment
}
else {
one_logger_client_sdk_1.logger.log(`Failed to extract class name from class declaration`);
}
}
}
}
}
}
}
catch (error) {
one_logger_client_sdk_1.logger.warn('Warning: Could not extract JSDoc comments:', { error });
}
one_logger_client_sdk_1.logger.log(`Final classScopes map:`, { classScopes });
return classScopes;
}
extractScopeFromJSDoc(className, jsDocScopes) {
return jsDocScopes.get(className) || 'singleton';
}
extractTypeAliases(root) {
const typeAliases = new Map();
try {
// Find all import statements and parse them manually
const allImports = root.findAll({
rule: {
kind: 'import_statement'
}
});
one_logger_client_sdk_1.logger.log(`Found ${allImports.length} total import statements`);
for (const importNode of allImports) {
const importText = importNode.text();
one_logger_client_sdk_1.logger.log(`Import statement: ${importText}`);
// Manual regex parsing - handles variable whitespace
const aliasMatch = importText.match(/import\s*{\s*([\w]+)\s+as\s+([\w]+)\s*}\s+from/);
if (aliasMatch) {
const [, original, alias] = aliasMatch;
typeAliases.set(alias.trim(), original.trim());
one_logger_client_sdk_1.logger.log(`Manual regex found type alias: ${alias.trim()} -> ${original.trim()}`);
}
}
}
catch (error) {
one_logger_client_sdk_1.logger.warn('Warning: Could not extract type aliases:', { error });
}
return typeAliases;
}
extractImportMappings(root) {
const importMappings = new Map();
try {
// Find all import statements
const allImports = root.findAll({
rule: {
kind: 'import_statement'
}
});
one_logger_client_sdk_1.logger.log(`Found ${allImports.length} import statements for mapping`);
for (const importNode of allImports) {
const importText = importNode.text();
one_logger_client_sdk_1.logger.log(`Processing import: ${importText}`);
// Extract import path
const pathMatch = importText.match(/from\s+['"]([^'"]+)['"]/);
if (!pathMatch)
continue;
const importPath = pathMatch[1];
// Handle different import patterns
// Named imports: import { A, B } from './path'
const namedImportsMatch = importText.match(/import\s*{\s*([^}]+)\s*}\s*from/);
if (namedImportsMatch) {
const namedImports = namedImportsMatch[1].split(',');
for (const namedImport of namedImports) {
const trimmed = namedImport.trim();
// Handle aliases: A as B
const aliasMatch = trimmed.match(/([\w]+)\s+as\s+([\w]+)/);
if (aliasMatch) {
const [, original, alias] = aliasMatch;
importMappings.set(alias.trim(), importPath);
one_logger_client_sdk_1.logger.log(`Mapped alias ${alias.trim()} -> ${importPath}`);
}
else {
importMappings.set(trimmed, importPath);
one_logger_client_sdk_1.logger.log(`Mapped named import ${trimmed} -> ${importPath}`);
}
}
}
// Default imports: import A from './path'
const defaultImportMatch = importText.match(/import\s+([\w]+)\s+from/);
if (defaultImportMatch && !namedImportsMatch) {
const defaultImport = defaultImportMatch[1];
importMappings.set(defaultImport, importPath);
one_logger_client_sdk_1.logger.log(`Mapped default import ${defaultImport} -> ${importPath}`);
}
}
}
catch (error) {
one_logger_client_sdk_1.logger.warn('Warning: Could not extract import mappings:', { error });
}
one_logger_client_sdk_1.logger.log(`Final import mappings:`, { importMappings });
return importMappings;
}
extractInterfaces(root) {
const interfaces = [];
try {
// Find all interface declarations
const interfaceNodes = root.findAll({
rule: {
kind: 'interface_declaration'
}
});
one_logger_client_sdk_1.logger.log(`Found ${interfaceNodes.length} interface declarations`);
for (const interfaceNode of interfaceNodes) {
const interfaceText = interfaceNode.text();
one_logger_client_sdk_1.logger.log(`Interface text: ${interfaceText}`);
// Extract interface name using regex
const interfaceMatch = interfaceText.match(/interface\s+([A-Za-z_][A-Za-z0-9_]*)/);
if (interfaceMatch) {
const interfaceName = interfaceMatch[1];
// Check if interface has at least one method
const methodNodes = interfaceNode.findAll({
rule: {
kind: 'method_signature'
}
});
// Consider interface valid if it has at least one method
if (methodNodes.length > 0) {
interfaces.push(interfaceName);
one_logger_client_sdk_1.logger.log(`Found valid interface: ${interfaceName} with ${methodNodes.length} methods `);
}
else {
one_logger_client_sdk_1.logger.log(`Skipping empty interface: ${interfaceName}`);
}
}
}
}
catch (error) {
one_logger_client_sdk_1.logger.warn('Warning: Could not extract interfaces:', { error });
}
one_logger_client_sdk_1.logger.log(`Final interfaces:`, { interfaces });
return interfaces;
}
}
exports.ASTParser = ASTParser;
//# sourceMappingURL=ast-parser.js.map