@tripsnek/tmf
Version:
TypeScript Modeling Framework - A TypeScript port of the Eclipse Modeling Framework (EMF)
232 lines (226 loc) • 9.32 kB
JavaScript
;
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