UNPKG

@tripsnek/tmf

Version:

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

232 lines (226 loc) 9.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TGeneratorPackageInitializer = void 0; const tgen_utils_1 = require("./tgen-utils"); /** * Generates a package initializer file for the root package that handles * initialization of the entire package hierarchy without circular imports. */ class TGeneratorPackageInitializer { /** * Generates the package initializer content for a root package. * @param rootPackage The root EPackage * @returns The generated TypeScript content */ generate(rootPackage) { if (rootPackage.getESuperPackage()) { throw new Error('Package initializer should only be generated for root packages'); } const allPackages = this.collectAllPackages(rootPackage); const className = TGeneratorPackageInitializer.generateClassName(rootPackage); const fileName = TGeneratorPackageInitializer.generateFileName(rootPackage); return this.generateContent(rootPackage, allPackages, className); } /** * Generates the class name for the package initializer. */ static generateClassName(rootPackage) { return tgen_utils_1.TGenUtils.capitalize(rootPackage.getName()) + 'PackageInitializer'; } /** * Generates the file name for the package initializer. */ static generateFileName(rootPackage) { return tgen_utils_1.TGenUtils.kebabLowerCase(rootPackage.getName()) + '-package-initializer'; } /** * Collects all packages in the hierarchy starting from the root. */ collectAllPackages(rootPackage) { const allPackages = []; this.collectPackagesRecursive(rootPackage, allPackages); return allPackages; } /** * Recursively collects all packages in the hierarchy. */ collectPackagesRecursive(pkg, collected) { collected.push(pkg); for (const subPackage of pkg.getESubPackages()) { this.collectPackagesRecursive(subPackage, collected); } } /** * Generates the full content of the package initializer file. */ generateContent(rootPackage, allPackages, className) { const imports = this.generateImports(allPackages); const classBody = this.generateClassBody(allPackages, className); return `${imports} ${classBody}`; } /** * Generates import statements for all packages. */ generateImports(allPackages) { let imports = ''; for (const pkg of allPackages) { const packageClassName = tgen_utils_1.TGenUtils.genPackageClassName(pkg); const packageFileName = tgen_utils_1.TGenUtils.genPackageFileName(pkg); const factoryClassName = tgen_utils_1.TGenUtils.genFactoryClassName(pkg); const factoryFileName = tgen_utils_1.TGenUtils.genFactoryFileName(pkg); // Calculate relative path from root to this package const relativePath = this.getRelativePathToPackage(pkg); imports += `import { ${packageClassName} } from '${relativePath}${packageFileName}';\n`; imports += `import { ${factoryClassName} } from '${relativePath}${factoryFileName}';\n`; } imports += `import { TJson, EClassImpl } from '@tripsnek/tmf';\n`; return imports; } /** * Calculates the relative path from root directory to a package directory. */ getRelativePathToPackage(pkg) { const pathParts = []; let current = pkg; // Build path from package up to root while (current.getESuperPackage()) { pathParts.unshift(current.getName()); current = current.getESuperPackage(); } // If this is the root package, use current directory if (pathParts.length === 0) { return './'; } // Build relative path return './' + pathParts.join('/') + '/'; } /** * Generates the class body with registerAll method. */ generateClassBody(allPackages, className) { const instanceDeclarations = this.generateInstanceDeclarations(allPackages); const packageRelationships = this.generatePackageRelationships(allPackages); const initializationCalls = this.generateInitializationCalls(allPackages); const pkgToFactoryRefs = this.generatePkgToFactoryRefs(allPackages); let names = ''; for (const p of allPackages) { if (names.length > 0) names += ','; names += tgen_utils_1.TGenUtils.uncapitalize(p.getName()); } return `/** * A "global initializer" solution for ensuring that package contents * for an entire package hierarchy can be initialized on the first * 'touch' of any individual package, without triggering circular import * issues. * * The way it works: * 1. Whenever any package is 'touched' (by simply being referenced in code) it * initialized it's '_eINSTANCE' field, and uses it to create its initial structures * and Literals references. This does *not* require touching other packages, so there * is no risk of circular imports. * 2. When the first invocation of '<package>.eINSTANCE' is made, each package intercepts * that as a static 'get' on the property, and calls registerAll() on this instance to ensure that * ALL packages are touched and have their initial contents created. * 3. The first time registerAll() is called, the package hierarchy (sub/super) is created, and * all package contents are initialized. */ export class ${className} { private static registered = false; static registerAll() { //if registration is completed, return immediately if (this.registered) return; this.registered = true; ${instanceDeclarations} ${packageRelationships} ${initializationCalls} ${pkgToFactoryRefs} const allPkgs = [${names}]; for (const p of allPkgs) { for (const e of p.getEClassifiers()) { if (e instanceof EClassImpl) { e.recomputeAllLists(); } } } //default TJson configuration TJson.addPackages(allPkgs); } }`; } /** * Generates the instance declarations in registerAll method. */ generateInstanceDeclarations(allPackages) { let declarations = ''; for (const pkg of allPackages) { const packageClassName = tgen_utils_1.TGenUtils.genPackageClassName(pkg); const variableName = tgen_utils_1.TGenUtils.uncapitalize(pkg.getName()); declarations += ` const ${variableName} = ${packageClassName}._eINSTANCE;\n`; } return declarations; } /** * Generates package relationship setup code. */ generatePackageRelationships(allPackages) { let relationships = '\n //set package/sub-package relationships\n'; for (const pkg of allPackages) { const superPackage = pkg.getESuperPackage(); if (superPackage) { const pkgVarName = tgen_utils_1.TGenUtils.uncapitalize(pkg.getName()); const superVarName = tgen_utils_1.TGenUtils.uncapitalize(superPackage.getName()); relationships += ` ${pkgVarName}.setESuperPackage(${superVarName});\n`; } } return relationships; } /** * Generates initialization method calls. */ generateInitializationCalls(allPackages) { let calls = '\n //initialize package contents\n'; // Initialize in dependency order (root first, then children) const sortedPackages = this.sortPackagesByDependency(allPackages); for (const pkg of sortedPackages) { const variableName = tgen_utils_1.TGenUtils.uncapitalize(pkg.getName()); calls += ` ${variableName}.initializePackageContents();\n`; } return calls; } generatePkgToFactoryRefs(allPackages) { let calls = '\n //initialize package to factory refs\n'; // Initialize in dependency order (root first, then children) const sortedPackages = this.sortPackagesByDependency(allPackages); for (const pkg of sortedPackages) { const variableName = tgen_utils_1.TGenUtils.uncapitalize(pkg.getName()); calls += ` ${variableName}.setEFactoryInstance(${tgen_utils_1.TGenUtils.genFactoryClassName(pkg)}.eINSTANCE);\n`; } return calls; } /** * Sorts packages by dependency order (parents before children). */ sortPackagesByDependency(packages) { const sorted = []; const processed = new Set(); const processPackage = (pkg) => { if (processed.has(pkg)) return; // Process super package first if it exists const superPackage = pkg.getESuperPackage(); if (superPackage && packages.includes(superPackage)) { processPackage(superPackage); } sorted.push(pkg); processed.add(pkg); }; for (const pkg of packages) { processPackage(pkg); } return sorted; } } exports.TGeneratorPackageInitializer = TGeneratorPackageInitializer; //# sourceMappingURL=tgenerator-package-initializer.js.map