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