UNPKG

@tripsnek/tmf

Version:

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

409 lines 15.7 kB
import { TUtils } from '../tutils'; import { EcorePackage } from '../metamodel/ecorepackage'; import { EAttributeImpl } from '../metamodel/eattribute-impl'; import { EEnumImpl } from '../metamodel/eenum-impl'; /** * Utility methods used for TMF source code generation, including * EPackage/EFactory specializations and individual EClassifier files. */ export class TGenUtils { //paths to which files are written static API_PATH = '/api'; static GEN_PATH = '/gen'; static IMPL_PATH = '/impl'; //imports needed by every api/gen/impl of every EClass static DEFAULT_IMPORTS = `import { EObject } from '@tripsnek/tmf'; import { TUtils } from '@tripsnek/tmf'; import { EStructuralFeature } from '@tripsnek/tmf'; import { BasicEList } from '@tripsnek/tmf'; import { EClass } from '@tripsnek/tmf'; import { EList } from '@tripsnek/tmf'; import { EEnum } from '@tripsnek/tmf'; import { EDataType } from '@tripsnek/tmf'; import { EObjectImpl } from '@tripsnek/tmf';`; static ECORE_DEFAULT_IMPORTS = `import { EObjectImpl } from '../impl/e-object'; import { EStructuralFeature } from '../api/e-structural-feature'; import { BasicEList } from '../common/basic-e-list'; import { EList } from '../common/e-list'; `; /** * Generates statements for importing a set of packages into a file residing * in the directory of the host package. * @param pkgToImport * @param host */ static generateImportStatementsForExternalPackages(pkgToImport, host, prefix) { let pkgImports = ''; for (const pkgDependency of pkgToImport) { //if we need to support importing EcorePackage, need to resolve it's path specially (or use an alias) if (pkgDependency !== EcorePackage.eINSTANCE) { if (pkgDependency) { const pkgName = TGenUtils.genPackageClassName(pkgDependency); //for nested package structures (allowable from Ecore) let pkgPath = this.getPathToOtherPackage(host, pkgDependency); //for packages defined as 'x-data' folders in lib without nesting // if (!host.getESuperPackage()) // pkgPath = this.getPathToOtherPackageFlat(host, pkgDependency); // if packages share a root, use relative imports if (host.getRootPackage() === pkgDependency.getRootPackage()) { pkgImports += `import { ${pkgName}} from '${prefix}${pkgPath}${this.kebabLowerCase(pkgDependency.getName())}-package';`; } else { console.log('WARNING: CROSS-PACKAGE IMPORTS NOT SUPPORTED'); } } } } return pkgImports; } /** * Generates import statements for the API .ts files for a set of EClassifiers. * @param imports */ static genApiImports(eclass, toImport, pathToApiDir) { let imports = ``; //add necessary imports for e.g. EOperations, cross-references and super types for (const ec of toImport) { if (ec) { let genPathToImport = `${pathToApiDir}/${this.genClassApiName(ec)}`; //construct path to the type, which may reside in another package if (ec.getEPackage() !== eclass.getEPackage()) { //if both eclasses are from the same root package, then we use relative paths if (ec.getRootPackage() === eclass.getRootPackage()) { //traverse from here up to root let pathToImport = TGenUtils.getPathToTypeInOtherPkg(eclass, ec); // pathToImport += 'api/' + ec.getName(); pathToImport += `api/${this.genClassApiName(ec)}`; genPathToImport = pathToImport; } // Otherwise, Alternate path-based import from another library else { console.log('WARNING: CROSS-PACKAGE IMPORTS NOT SUPPORTED'); } // console.log( // eclass.getEPackage().getName() + ' to ' + ec.getEPackage().getName() + ' ' + genPathToImport // ); } imports += `import { ${ec.getName()} } from '${genPathToImport}';\n`; } } return imports; } /** * Method signature for an EOperation. * @param eop */ static eopSignature(eop) { let signature = `${eop.getName()}(`; //determine return type const returnType = TGenUtils.eopReturnType(eop); //add parameters let paramInd = 0; for (const param of eop.getEParameters()) { if (param.getEType()) { if (paramInd > 0) signature += ', '; let paramType = TUtils.getTypescriptName(param.getEType()); if (param.isMany()) { paramType = `EList<${paramType}>`; } const paramName = param.getName() + (param.isOptional() ? '?' : ''); signature += `${paramName}: ${paramType}`; paramInd++; } } return `${signature}): ${returnType}`; } static eopReturnType(eop) { let returnType = eop.getEType() ? TUtils.getTypescriptName(eop.getEType()) : 'void'; //handle lists if (eop.isMany()) { returnType = `EList<${returnType}>`; } return returnType; } /** * Invocation of an EOperation. * * @param eop */ static eopInvocation(eop) { let signature = `${eop.getName()}(`; //add parameters let paramInd = 0; for (const param of eop.getEParameters()) { if (paramInd > 0) signature += ','; signature += `${param.getName()}`; paramInd++; } return `${signature})`; } static genGenClassName(eclass) { return eclass.getName() + 'Gen'; } static genImplClassName(eclass) { return eclass.getName() + 'Impl'; } /** * Method name for feature setter. * @param f */ static setterName(f) { return 'set' + TGenUtils.capitalize(f.getName()); } static basicSetterName(f) { return `basic${TGenUtils.capitalize(TGenUtils.setterSig(f))}`; } /** * Method name for feature getter. * @param f */ static getterName(f) { return ((f.getEType()?.getName() === 'EBoolean' ? 'is' : 'get') + TGenUtils.capitalize(f.getName())); } /** * Method signature for feature setter. * @param f */ static setterSig(f) { return `${TGenUtils.setterName(f)}(${TGenUtils.setterParamName(f)}: ${TGenUtils.getTypeName(f)}): void`; } /** * Method signature for feature basic setter. * @param f */ static basicSetterSig(f) { return `${TGenUtils.basicSetterName(f)}(${TGenUtils.setterParamName(f)}: ${TGenUtils.getTypeName(f)}): void`; } static setterParamName(f) { return `new${TGenUtils.capitalize(f.getName())}`; } /** * Method signature for feature getter. * @param f */ static getterSig(f) { return `${TGenUtils.getterName(f)}(): ${TGenUtils.getTypeName(f)}`; } /** * Gets the name of the data type for the given feature. * @param f */ static getTypeName(f) { let typeName = f.getEType()?.getName ? f.getEType()?.getName() : 'any'; if (f instanceof EAttributeImpl) { if (f.getEType() instanceof EEnumImpl) { typeName = f.getEType()?.getName(); } else { typeName = TUtils.toTypeScriptPrimitive(f.getEType()); } } if (f.isMany()) { typeName = `EList<${typeName}>`; } return typeName; } //====================================================================== // Basic string manipulation for naming (e.g., casing) /** * Capitalizes the first letter of the given string. * @param s */ static capitalize(s) { return !s ? '' : s.charAt(0).toUpperCase() + s.slice(1); } /** * First letter to lower case. * @param s */ static uncapitalize(s) { return !s ? '' : s.charAt(0).toLowerCase() + s.slice(1); } /** * Converts the string to snake case (with underscores preceding * upper letters), and then to all upper case. * * @param str */ static snakeUpperCase(str) { let converted = str .replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`) .toUpperCase(); if (converted.startsWith('_')) { converted = converted.substring(1); } return converted; } /** * Converts the string to kebab case (with dashes preceding * upper letters), and then to all lower case. * * @param str */ static kebabLowerCase(str) { let converted = str .replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`) .toLowerCase(); if (converted.startsWith('-')) { converted = converted.substring(1); } return converted; } //====================================================================== // Generate specific package, class, and/or field -associated names /** * Generates camel-case class name from original '-'separated package name * with some optional suffix (like 'Factory' or 'Package') tacked on. * @param pkg * @param suffix */ static genPkgClassName(pkg, suffix) { if (!pkg) return 'ErrorNoSuchPackage' + suffix; const pkgNameParts = pkg.getName().split('-').map(this.capitalize); pkgNameParts.push(suffix); return pkgNameParts.join(''); } static genPackageClassName(pkg) { return this.genPkgClassName(pkg, 'Package'); } static genPackageFileName(pkg) { return this.kebabLowerCase(pkg.getName()) + '-package'; } static genUtilsFileName(pkg) { return this.kebabLowerCase(pkg.getName()) + '-utils'; } static genFactoryClassName(pkg) { return this.genPkgClassName(pkg, 'Factory'); } static genFactoryFileName(pkg) { return this.kebabLowerCase(pkg.getName()) + '-factory'; } static genClassApiName(eClass, async) { return this.kebabLowerCase(eClass.getName()) + (async ? '-async' : ''); } static genClassGenName(eClass, async) { return (this.kebabLowerCase(eClass.getName()) + '-gen' + (async ? '-async' : '')); } static genClassImplName(eClass) { return this.kebabLowerCase(eClass.getName()) + '-impl'; } static genClassIdFieldName(eclassifier) { return this.snakeUpperCase(eclassifier.getName()); } static genFeatureIdFieldName(feature) { return (this.snakeUpperCase(feature.getEContainingClass().getName()) + '__' + this.snakeUpperCase(feature.getName())); } static genOperationIdFieldName(feature) { return (this.snakeUpperCase(feature.getEContainingClass().getName()) + '__' + this.snakeUpperCase(feature.getName())); } static genFeatureCountFieldName(eclass) { return this.snakeUpperCase(eclass.getName()) + '_FEATURE_COUNT'; } static genFeatureGetterName(feature) { const className = feature.getEContainingClass().getName(); const featureName = this.capitalize(feature.getName()); return `get${className}_${featureName}`; } static genOperationGetterName(feature) { const className = feature.getEContainingClass().getName(); const featureName = this.capitalize(feature.getName()); return `get${className}_${featureName}`; } static genEdataTypeGetter(feature) { return `EcorePackage.eINSTANCE.get${this.capitalize(feature.getName())}()`; } static genEclassGetterName(eclassifier) { const getter = 'get' + this.capitalize(eclassifier.getName()); return getter; } //====================================================================== /** * Generates reference to the package instance for the 'to' EClass, and * registers it as a package to import if it is different from that * of the 'from' EClass. * * @param to * @param from * @param pkgToImport */ static getReferenceToPackageInstance(to, from, pkgToImport) { let pkgRef = 'this'; //handle inheritance from objects in other packages if (to.getEPackage() !== from.getEPackage()) { pkgToImport.add(to.getEPackage()); pkgRef = TGenUtils.genPackageClassName(to.getEPackage()) + '.eINSTANCE'; } return pkgRef; } static getPathToRoot(pkg) { let pathToRoot = './'; if (pkg.getESuperPackage()) { let cursor = pkg; pathToRoot = ''; while (cursor.getESuperPackage()) { pathToRoot += '../'; cursor = cursor.getESuperPackage(); } } return pathToRoot; } //====================================================================== // File/path related methods (TODO: Should use imported PATH library???) /** * Computes the import path necessary to resolve an EClass in a different * package. * @param fromClass * @param toClass */ static getPathToTypeInOtherPkg(fromClass, toClass) { //TODO: If cross-package references are not being correctly computed, it //may be because eclass.setEPackage is not being called during Ecore parsing. //Correct fix would be for this to be set as part of a proper inverse //reference with EPackage.eclassifiers const fromPackage = fromClass.getEPackage(); const toPackage = toClass.getEPackage(); //for nested package structures (allowable from Ecore) if (fromPackage.getESuperPackage()) return '../' + this.getPathToOtherPackage(fromPackage, toPackage); //for packages defined as 'x-data' folders in lib without nesting return '../' + this.getPathToOtherPackageFlat(fromPackage, toPackage); } static getPathToOtherPackageFlat(fromPackage, toPackage) { const pkgName = toPackage ? toPackage.getName() : 'PackageNotFound'; console.log('WARNING: CROSS-PACKAGE IMPORTS NOT SUPPORTED'); return `'${this.kebabLowerCase(pkgName)}'`; } static getPathToOtherPackage(fromPackage, toPackage) { let pathToImport = ''; let cursor = fromPackage; while (cursor.getESuperPackage()) { pathToImport += '../'; cursor = cursor.getESuperPackage(); } //and back down to the element to be imported const pathDown = new Array(); cursor = toPackage; while (cursor.getESuperPackage()) { pathDown.unshift(cursor); cursor = cursor.getESuperPackage(); } for (const pathPkg of pathDown) { pathToImport += pathPkg.getName() + '/'; } return pathToImport; } static inSameLib(eclass1, eclass2) { return eclass1.getRootPackage() === eclass2.getRootPackage(); } } //# sourceMappingURL=tgen-utils.js.map