UNPKG

@tripsnek/tmf

Version:

TypeScript Modeling Framework - A TypeScript port of the Eclipse Modeling Framework (EMF)

529 lines 24.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TGeneratorMain = void 0; exports.generate = generate; const tgenerator_package_1 = require("./tgenerator-package"); const tgenerator_factory_1 = require("./tgenerator-factory"); const tgenerator_utils_1 = require("./tgenerator-utils"); const tgenerator_gen_1 = require("./tgenerator-gen"); const tgenerator_api_1 = require("./tgenerator-api"); const tgenerator_impl_1 = require("./tgenerator-impl"); const tgenerator_enum_1 = require("./tgenerator-enum"); const tgen_utils_1 = require("./tgen-utils"); const ecorepackage_1 = require("../metamodel/ecorepackage"); const tgenerator_barrel_index_1 = require("./tgenerator-barrel-index"); const eclass_impl_1 = require("../metamodel/eclass-impl"); const ereference_impl_1 = require("../metamodel/ereference-impl"); const eenum_impl_1 = require("../metamodel/eenum-impl"); const environment_1 = require("../utils/environment"); const tgenerator_package_initializer_1 = require("./tgenerator-package-initializer"); const basicelist_1 = require("../metamodel/basicelist"); // Debug flag - set to true to enable detailed logging const DEBUG = false; // Debug logging utility const debug = { log: (message, ...args) => { if (DEBUG) { console.log(`[DEBUG] ${message}`, ...args); } }, time: (label) => { if (DEBUG) { console.time(`[DEBUG] ${label}`); } }, timeEnd: (label) => { if (DEBUG) { console.timeEnd(`[DEBUG] ${label}`); } }, group: (label) => { if (DEBUG) { console.group(`[DEBUG] ${label}`); } }, groupEnd: () => { if (DEBUG) { console.groupEnd(); } }, warn: (message, ...args) => { if (DEBUG) { console.warn(`[DEBUG WARN] ${message}`, ...args); } }, error: (message, ...args) => { if (DEBUG) { console.error(`[DEBUG ERROR] ${message}`, ...args); } }, }; /** * Exposes source code generation. Only available in Node.js environment. * @param pkg * @param outDir * @param overwriteImpl */ async function generate(pkg, outDir, overwriteImpl) { debug.time('Total Generation Time'); debug.log('Starting code generation for package:', pkg.getName() || 'unnamed package'); debug.log('Output directory:', outDir); debug.log('Overwrite implementation files:', overwriteImpl); environment_1.Environment.requireNodeEnvironment('Code generation'); const generator = new TGeneratorMain(pkg, outDir, overwriteImpl); await generator.generate(); debug.timeEnd('Total Generation Time'); debug.log('Code generation completed successfully'); } /** * Generates TypeScript source code for an EPackage and all subpackages, including * the following artifacts: * (1) *-package.ts for each EPackage (see TMFPackageSourceGenerator.ts) * (2) *-factory.ts for each EPackage (see TMFFactorySourceGenerator.ts) * (3) API interfaces (see TMFApiSourceGenerator.ts) * (4) Abstract *Gen.ts classes (see TMFGenSourceGenerator.ts) * (5) Overwritable *Impl.ts classes (see TMFImplSourceGenerator.ts) * (6) Enums (see TMFEnumSourceGenerator.ts) */ class TGeneratorMain { pkg; outDir; overwriteImpl; barrelFileDir; // NOTE: These 5 are all constants computed during initialization barrelFileOutDir; pkgOutDir; pkgRootOutDir; pkgApiOutDir; pkgGenOutDir; pkgImplOutDir; // NOTE: These 3 are recomputed for each EClass! eClassApiImports; eClassGenImports; eClassPkgImports; // Cached Node.js modules (loaded once per instance) fs = null; path = null; childProcess = null; constructor(pkg, outDir, overwriteImpl = false, barrelFileDir) { this.pkg = pkg; this.outDir = outDir; this.overwriteImpl = overwriteImpl; this.barrelFileDir = barrelFileDir; debug.log('Initializing TGeneratorMain for package:', pkg.getName() || 'unnamed'); debug.log('Constructor parameters:', { packageName: pkg.getName(), outDir, overwriteImpl, barrelFileDir, }); // Initialize directories without file system operations this.pkgOutDir = this.outDir + (pkg.getName() ? '/' + pkg.getName() : ''); this.pkgRootOutDir = this.pkgOutDir; this.pkgApiOutDir = this.pkgOutDir + tgen_utils_1.TGenUtils.API_PATH; this.pkgGenOutDir = this.pkgOutDir + tgen_utils_1.TGenUtils.GEN_PATH; this.pkgImplOutDir = this.pkgOutDir + tgen_utils_1.TGenUtils.IMPL_PATH; debug.log('Computed directory paths:', { pkgOutDir: this.pkgOutDir, pkgRootOutDir: this.pkgRootOutDir, pkgApiOutDir: this.pkgApiOutDir, pkgGenOutDir: this.pkgGenOutDir, pkgImplOutDir: this.pkgImplOutDir, }); // Note: barrelFileOutDir will be set during generate() when path is available } /** * Generates typescript sourcecode for all packages, recursing all subpackages. * Only available in Node.js environment. */ async generate(attemptFormatWithPrettier) { debug.time(`Generation for package: ${this.pkg.getName() || 'unnamed'}`); debug.group(`Generating package: ${this.pkg.getName() || 'unnamed'}`); environment_1.Environment.requireNodeEnvironment('Code generation'); // Load Node.js modules debug.log('Loading Node.js modules...'); await this.ensureNodeModulesLoaded(); // Now we can resolve directories and create them debug.log('Initializing directories...'); await this.initializeDirectories(); // Count classifiers for debug info const classifiers = this.pkg.getEClassifiers(); const eClasses = classifiers.filter((c) => c instanceof eclass_impl_1.EClassImpl); const eEnums = classifiers.filter((c) => c instanceof eenum_impl_1.EEnumImpl); const subpackages = this.pkg.getESubPackages(); debug.log('Package contents:', { totalClassifiers: classifiers.size(), eClasses: eClasses.size(), eEnums: eEnums.size(), subpackages: subpackages.size(), }); // Generate all the files debug.log('Generating core files...'); if (this.barrelFileDir) { debug.log('Writing barrel file...'); await this.writeBarrelFile(); } debug.log('Writing package file...'); await this.writePackageFile(); debug.log('Writing factory file...'); await this.writeFactoryFile(); debug.log('Writing utils file...'); await this.writeUtilsFile(); // Add this new section: debug.log('Writing package initializer file...'); await this.writePackageInitializerFile(); // Generate EClass artifacts debug.log(`Processing ${eClasses.size()} EClass(es)...`); for (const eClassifier of classifiers) { if (eClassifier instanceof eclass_impl_1.EClassImpl) { debug.log(`Generating artifacts for EClass: ${eClassifier.getName()}`); await this.generateAllEClassArtifacts(eClassifier); } else if (eClassifier instanceof eenum_impl_1.EEnumImpl) { debug.log(`Generating enum file for: ${eClassifier.getName()}`); await this.writeEnumFile(eClassifier); } } // Deal with recursive subpackages debug.log(`Processing ${subpackages.size()} subpackage(s)...`); for (const subpackage of subpackages) { debug.log(`Recursively generating subpackage: ${subpackage.getName()}`); const subGenerator = new TGeneratorMain(subpackage, this.pkgRootOutDir, this.overwriteImpl); await subGenerator.generate(); } // Finally... format the source code with prettier if (attemptFormatWithPrettier) { debug.log('Attempting to format code with Prettier...'); await this.tryFormatWithPrettier(); } debug.groupEnd(); debug.timeEnd(`Generation for package: ${this.pkg.getName() || 'unnamed'}`); debug.log(`Completed generation for package: ${this.pkg.getName() || 'unnamed'}`); } async generateAllEClassArtifacts(eClassifier) { debug.group(`Generating EClass artifacts: ${eClassifier.getName()}`); debug.time(`EClass generation: ${eClassifier.getName()}`); debug.log('Setting up imports...'); this.setupEClassImports(eClassifier); debug.log('Import analysis results:', { apiImports: Array.from(this.eClassApiImports).map((c) => c.getName()), genImports: Array.from(this.eClassGenImports).map((c) => c.getName()), packageImports: Array.from(this.eClassPkgImports).map((p) => p.getName()), }); debug.log('Writing API file...'); await this.writeEClassApiFile(eClassifier); debug.log('Writing Gen file...'); await this.writeEClassGenFile(eClassifier); debug.log('Writing Impl file...'); await this.writeEClassImplFile(eClassifier); debug.timeEnd(`EClass generation: ${eClassifier.getName()}`); debug.groupEnd(); } /** * Check if code generation is supported in current environment */ static isGenerationSupported() { const supported = environment_1.Environment.isNode; debug.log('Generation support check:', supported); return supported; } /** * Get list of supported operations in current environment */ static getSupportedOperations() { const operations = ['In-memory generation']; if (environment_1.Environment.isNode) { operations.push('File-based generation', 'Directory creation', 'Prettier formatting', 'Recursive package processing'); } debug.log('Supported operations:', operations); return operations; } // ====================================================================== // Private methods async ensureNodeModulesLoaded() { debug.log('Loading Node.js modules...'); if (!this.fs) { debug.log('Loading fs module...'); this.fs = await environment_1.ConditionalImports.getNodeModule('fs'); } if (!this.path) { debug.log('Loading path module...'); this.path = await environment_1.ConditionalImports.getNodeModule('path'); } // childProcess is loaded on-demand in tryFormatWithPrettier debug.log('Node.js modules loaded successfully'); } async initializeDirectories() { debug.log('Initializing and creating directories...'); // Now we can use path operations this.pkgRootOutDir = await this.resolveAndMaybeCreateDir(this.pkgOutDir, false); this.pkgApiOutDir = await this.resolveAndMaybeCreateDir(this.pkgOutDir + tgen_utils_1.TGenUtils.API_PATH, true); this.pkgGenOutDir = await this.resolveAndMaybeCreateDir(this.pkgOutDir + tgen_utils_1.TGenUtils.GEN_PATH, true); this.pkgImplOutDir = await this.resolveAndMaybeCreateDir(this.pkgOutDir + tgen_utils_1.TGenUtils.IMPL_PATH, false); if (this.barrelFileDir) { this.barrelFileOutDir = this.path.resolve(__dirname, this.barrelFileDir); debug.log('Barrel file output directory set to:', this.barrelFileOutDir); } debug.log('Directory initialization completed'); } async writeBarrelFile() { debug.log('Generating barrel file content...'); const generator = new tgenerator_barrel_index_1.TGeneratorBarrelIndexTs(); const content = await generator.generate(this.pkg, this.barrelFileOutDir); debug.log('Writing barrel file to:', this.barrelFileOutDir); await this.writeSourceFile(this.barrelFileOutDir, 'index.ts', content, true); } async writePackageFile() { const fileName = tgen_utils_1.TGenUtils.genPackageFileName(this.pkg); debug.log('Generating package file:', fileName + '.ts'); const generator = new tgenerator_package_1.TGeneratorPackage(); const content = generator.generatePackageContents(this.pkg); await this.writeSourceFile(this.pkgRootOutDir, fileName + '.ts', content, true); } async writeFactoryFile() { const fileName = tgen_utils_1.TGenUtils.genFactoryFileName(this.pkg); debug.log('Generating factory file:', fileName + '.ts'); const generator = new tgenerator_factory_1.TGeneratorFactory(); const content = generator.generateFactoryContents(this.pkg); await this.writeSourceFile(this.pkgRootOutDir, fileName + '.ts', content, true); } async writeUtilsFile() { const fileName = tgen_utils_1.TGenUtils.genUtilsFileName(this.pkg); debug.log('Generating utils file:', fileName + '.ts'); const generator = new tgenerator_utils_1.TGeneratorUtils(); const content = generator.generateUtilsContents(this.pkg); await this.writeSourceFile(this.pkgRootOutDir, fileName + '.ts', content, false); } setupEClassImports(eClass) { debug.log(`Setting up imports for EClass: ${eClass.getName()}`); // Determines the set of types that needs to be imported to support the EClass this.eClassApiImports = new Set(); this.eClassGenImports = new Set(); this.eClassPkgImports = new Set(); debug.log('Collecting imports...'); this.collectEClassImports(this.pkg, eClass); debug.log('Import collection completed'); } async writeEClassApiFile(eClass) { const filename = tgen_utils_1.TGenUtils.genClassApiName(eClass); debug.log(`Writing API file for ${eClass.getName()}: ${filename}.ts`); const apiGenerator = new tgenerator_api_1.TGeneratorApi(); const tsInterfaceContent = apiGenerator.generate(eClass, this.eClassApiImports); await this.writeSourceFile(this.pkgApiOutDir, filename + '.ts', tsInterfaceContent, true); } async writeEClassGenFile(eClass) { const filename = tgen_utils_1.TGenUtils.genClassGenName(eClass); debug.log(`Writing Gen file for ${eClass.getName()}: ${filename}.ts`); const genGenerator = new tgenerator_gen_1.TGeneratorGen(); const tsGenClassContent = genGenerator.generate(eClass, this.eClassApiImports, this.eClassGenImports, this.eClassPkgImports); await this.writeSourceFile(this.pkgGenOutDir, filename + '.ts', tsGenClassContent, true); } async writeEClassImplFile(eClass) { const filename = tgen_utils_1.TGenUtils.genClassImplName(eClass); debug.log(`Writing Impl file for ${eClass.getName()}: ${filename}.ts`); debug.log(`Overwrite mode: ${this.overwriteImpl}`); const implGenerator = new tgenerator_impl_1.TGeneratorImpl(); const tsImplClassContent = implGenerator.generate(eClass, this.eClassApiImports); await this.writeSourceFile(this.pkgImplOutDir, filename + '.ts', tsImplClassContent, this.overwriteImpl); } async writeEnumFile(eclassifier) { const filename = tgen_utils_1.TGenUtils.genClassApiName(eclassifier) + '.ts'; debug.log(`Writing enum file for ${eclassifier.getName()}: ${filename}`); const enumGenerator = new tgenerator_enum_1.TGeneratorEnum(); const enumContent = enumGenerator.generate(eclassifier); await this.writeSourceFile(this.pkgApiOutDir, filename, enumContent, true); } async resolveAndMaybeCreateDir(outDir, deleteAllInDir) { await this.ensureNodeModulesLoaded(); const resolvedOutDir = this.path.resolve(__dirname, outDir); debug.log(`Processing directory: ${outDir} -> ${resolvedOutDir}`); debug.log(`Delete all files in directory: ${deleteAllInDir}`); if (this.fs.existsSync(resolvedOutDir)) { debug.log('Directory exists'); if (deleteAllInDir) { debug.log('Deleting all files in directory...'); await this.deleteFilesInFolder(resolvedOutDir); } } else { debug.log('Directory does not exist, creating...'); this.fs.mkdirSync(resolvedOutDir, { recursive: true }); debug.log('Directory created successfully'); } return outDir; } /** * Traverses entire EClass structure and collects the set of types that need * to be imported, including API, Gen and EPackage instances. */ collectEClassImports(pkg, eClass) { debug.group(`Collecting imports for EClass: ${eClass.getName()}`); // Start with importing supertypes const superTypes = eClass.getESuperTypes(); debug.log(`Processing ${superTypes.size()} super type(s)...`); for (const superType of superTypes) { debug.log(`Adding super type: ${superType.getName()}`); this.eClassApiImports.add(superType); if (!superType.isInterface()) { this.eClassGenImports.add(superType); debug.log(`Added to Gen imports (not interface): ${superType.getName()}`); } } // Add imports to support EStructuralFeatures const features = eClass.getEAllStructuralFeatures(); debug.log(`Processing ${features.size()} structural feature(s)...`); for (const field of features) { if (field.getEType()) { this.maybeAddMemberImports(pkg, eClass, field.getEType()); if (field instanceof ereference_impl_1.EReferenceImpl) { const opposite = field.getEOpposite(); if (opposite) { debug.log(`Processing opposite reference: ${opposite.getName()}`); this.maybeAddMemberImports(pkg, eClass, opposite.getEContainingClass()); } } } } // Add imports to support EOperations const operationsToImplement = this.eopsToImplement(eClass); debug.log(`Processing ${operationsToImplement.size()} operation(s) for ${eClass.getName()}...`); for (const eop of operationsToImplement) { if (eop.getEType()) this.maybeAddMemberImports(pkg, eClass, eop.getEType()); const params = eop.getEParameters(); for (const param of params) { if (param.getEType()) { // make sure to import any parameter type, if necessary this.maybeAddMemberImports(pkg, eClass, param.getEType()); } } } debug.groupEnd(); } eopsToImplement(eClass) { let operationsToImplement = new basicelist_1.BasicEList(eClass.getEOperations().elements()); for (const st of eClass.getESuperTypes()) { if (st.isInterface()) { operationsToImplement.addAll(st.getEOperations().elements()); } } return operationsToImplement; } /** * Possibly adds the type and package of 'etype' to the list of things * to import. */ maybeAddMemberImports(thisPackage, thisEClass, candidate) { debug.log(`Evaluating import candidate: ${candidate?.getName()}`); if (candidate instanceof eclass_impl_1.EClassImpl || candidate instanceof eenum_impl_1.EEnumImpl) { const candidatePackage = candidate.getEPackage(); debug.log(`Candidate package: ${candidatePackage.getName()}, this package: ${thisPackage.getName()}`); if (candidatePackage !== thisPackage) { debug.log(`Adding package import: ${candidatePackage.getName()}`); this.eClassPkgImports.add(candidatePackage); } if (candidate !== thisEClass) { // Ecore types are imported for everything if (candidatePackage !== ecorepackage_1.EcorePackage.eINSTANCE && !this.eClassApiImports.has(candidate)) { debug.log(`Adding API import: ${candidate.getName()}`); this.eClassApiImports.add(candidate); } if (candidatePackage !== ecorepackage_1.EcorePackage.eINSTANCE && !this.eClassGenImports.has(candidate)) { debug.log(`Adding Gen import for ${thisEClass.getName()}: ${candidate.getName()}`); this.eClassGenImports.add(candidate); } else { debug.log(`Skipping Ecore type: ${candidate.getName()}`); } } else { debug.log(`Skipping import (already exists or is self): ${candidate.getName()}`); } } } // Add this method to the TGeneratorMain class async writePackageInitializerFile() { // Only generate for root packages (packages without a super package) if (this.pkg.getESuperPackage()) { debug.log('Skipping package initializer for ' + this.pkg.getName() + ' - not a root package'); return; } const generator = new tgenerator_package_initializer_1.TGeneratorPackageInitializer(); const fileName = tgenerator_package_initializer_1.TGeneratorPackageInitializer.generateFileName(this.pkg); debug.log('Generating package initializer file:', fileName + '.ts'); const content = generator.generate(this.pkg); await this.writeSourceFile(this.pkgRootOutDir, fileName + '.ts', content, true); } /** * Writes the sourcecode file to the file system. * @param outDir * @param filename * @param content * @param overwrite */ async writeSourceFile(outDir, filename, content, overwrite) { await this.ensureNodeModulesLoaded(); const filePath = this.path.resolve(__dirname, outDir + '/' + filename); debug.log(`Writing file: ${filePath}`); debug.log(`Overwrite: ${overwrite}, Content length: ${content.length} characters`); if (overwrite || !this.fs.existsSync(filePath)) { this.fs.writeFileSync(filePath, content, 'utf8'); debug.log(`File written successfully: ${filename}`); } else { debug.log(`File skipped (already exists, overwrite=false): ${filename}`); } } async deleteFilesInFolder(directoryPath) { await this.ensureNodeModulesLoaded(); debug.log(`Deleting files in folder: ${directoryPath}`); try { const files = this.fs.readdirSync(directoryPath); debug.log(`Found ${files.length} file(s) to delete`); for (const file of files) { try { const filePath = this.path.join(directoryPath, file); this.fs.unlinkSync(filePath); debug.log(`Deleted file: ${file}`); } catch (err) { // Swallow individual file deletion errors as per original logic debug.warn(`Failed to delete file ${file}:`, err); } } debug.log('File deletion completed'); } catch (err) { debug.error(`Failed to read directory ${directoryPath}:`, err); } } async tryFormatWithPrettier() { debug.log('Attempting to format code with Prettier...'); // Fallback to command line prettier try { if (!this.childProcess) { debug.log('Loading child_process module...'); this.childProcess = await environment_1.ConditionalImports.getNodeModule('child_process'); } // Format main output directory const mainPath = this.path.resolve(__dirname, this.outDir); debug.log(`Formatting main directory with CLI: ${mainPath}`); this.childProcess.execSync(`prettier --write "${mainPath}"`); debug.log('Main directory formatted successfully'); // Format barrel file directory if it exists if (this.barrelFileDir) { const barrelPath = this.path.resolve(__dirname, this.barrelFileOutDir); debug.log(`Formatting barrel directory with CLI: ${barrelPath}`); this.childProcess.execSync(`prettier --write "${barrelPath}"`); debug.log('Barrel directory formatted successfully'); } debug.log('CLI Prettier formatting completed successfully'); } catch (error) { debug.warn('Prettier formatting failed:', error.message); debug.warn('Make sure prettier is installed: npm install -g prettier'); } } } exports.TGeneratorMain = TGeneratorMain; //# sourceMappingURL=tgenerator-main.js.map