From 913b95d33debcf8d7f666a7f5706aab1fd9bfa30 Mon Sep 17 00:00:00 2001 From: Jaskeerat Singh Saluja <58400083+salujajaskeerat@users.noreply.github.com> Date: Thu, 11 Jul 2024 13:40:52 +0530 Subject: [PATCH] feat(alias-import): update runtime classes to understand import aliasing (#860) * feat(alias import): Optional field aliasImport added to basemodelmanager - TODO marked - aliasImport handling in runtime separated from rest code to prevent breaking changes Signed-off-by: Jaskeerat Singh Saluja <58400083+salujajaskeerat@users.noreply.github.com> * feat(alias import): modelfile.js alias imports handled - alias types mapped to fqn in the importShortNames Signed-off-by: Jaskeerat Singh Saluja <58400083+salujajaskeerat@users.noreply.github.com> * feat(import alias):concerto-core modefile update - Handled PR changes - types can't be aliased to primitive types Signed-off-by: Jaskeerat Singh Saluja <58400083+salujajaskeerat@users.noreply.github.com> * feat(alias import): Test Cases added Following are the test-cases added: - resolve alias type from import - validate alias type - validate map using alias type as value Signed-off-by: Jaskeerat Singh Saluja <58400083+salujajaskeerat@users.noreply.github.com> * feat(alias import): Types updated ; Minor version updated Signed-off-by: Jaskeerat Singh Saluja <58400083+salujajaskeerat@users.noreply.github.com> * feat(alias): Pr suggestion added - Added new test case where a concept is extended on a aliased import type concept - removed unecessary comments and formated code Signed-off-by: Jaskeerat Singh Saluja <58400083+salujajaskeerat@users.noreply.github.com> * feat(alias): aliasName renamed to aliasedName Signed-off-by: Jaskeerat Singh Saluja <58400083+salujajaskeerat@users.noreply.github.com> --------- Signed-off-by: Jaskeerat Singh Saluja <58400083+salujajaskeerat@users.noreply.github.com> --- packages/concerto-core/api.txt | 3 +- packages/concerto-core/changelog.txt | 3 + .../concerto-core/lib/basemodelmanager.js | 10 ++ .../concerto-core/lib/introspect/modelfile.js | 34 +++++- packages/concerto-core/package.json | 2 +- .../test/introspect/modelfile.js | 109 ++++++++++++++++++ .../types/lib/basemodelmanager.d.ts | 9 ++ 7 files changed, 164 insertions(+), 6 deletions(-) diff --git a/packages/concerto-core/api.txt b/packages/concerto-core/api.txt index 0b9e4ed67..36933741a 100644 --- a/packages/concerto-core/api.txt +++ b/packages/concerto-core/api.txt @@ -2,9 +2,10 @@ class AstModelManager extends BaseModelManager { + void constructor(object?) } class BaseModelManager { - + void constructor(object?,boolean?,Object?,boolean?,boolean?,boolean?,processFile?) + + void constructor(object?,boolean?,Object?,boolean?,boolean?,boolean?,boolean?,processFile?) + boolean isModelManager() + boolean isStrict() + + boolean isAliasedTypeEnabled() + Object accept(Object,Object) + void validateModelFile(string|ModelFile,string?) throws IllegalModelException + Object addModelFile(ModelFile,string?,string?,boolean?) throws IllegalModelException diff --git a/packages/concerto-core/changelog.txt b/packages/concerto-core/changelog.txt index 445dfbea9..730801583 100644 --- a/packages/concerto-core/changelog.txt +++ b/packages/concerto-core/changelog.txt @@ -24,6 +24,9 @@ # Note that the latest public API is documented using JSDocs and is available in api.txt. # +Version 3.17.1 {fc7abec4765b5e1672dab7c6032dfdc3} 2024-06-21 +- Added 'enableAliasedType' option to BaseModelManager +- Aliased types mapped to FQN in modelfile Version 3.16.8 {a23d37a4a92071314ff821f879943364} 2024-07-09 - Added a new pathway for applying dcs target at namespace diff --git a/packages/concerto-core/lib/basemodelmanager.js b/packages/concerto-core/lib/basemodelmanager.js index 9c2a36f1c..0052a94d8 100644 --- a/packages/concerto-core/lib/basemodelmanager.js +++ b/packages/concerto-core/lib/basemodelmanager.js @@ -82,6 +82,7 @@ class BaseModelManager { * @param {boolean} [options.metamodelValidation] - When true, modelfiles will be validated * @param {boolean} [options.addMetamodel] - When true, the Concerto metamodel is added to the model manager * @param {boolean} [options.enableMapType] - When true, the Concerto Map Type feature is enabled + * @param {boolean} [options.enableAliasedType] - When true, the Concerto Aliasing feature is enabled * @param {*} [processFile] - how to obtain a concerto AST from an input to the model manager */ constructor(options, processFile) { @@ -97,6 +98,7 @@ class BaseModelManager { // TODO Remove on release of MapType // Supports both env var and property based flag this.enableMapType = !!options?.enableMapType; + this.enableAliasedType = !!options?.enableAliasedType; // Cache a copy of the Metamodel ModelFile for use when validating the structure of ModelFiles later. this.metamodelModelFile = new ModelFile(this, MetaModelUtil.metaModelAst, undefined, MetaModelNamespace); @@ -122,6 +124,14 @@ class BaseModelManager { return this.strict; } + /** + * Checks if the import aliasing feature is enabled. + * @returns {boolean} true if the enableAliasedType has been set + */ + isAliasedTypeEnabled() { + return this.enableAliasedType; + } + /** * Adds root types * @private diff --git a/packages/concerto-core/lib/introspect/modelfile.js b/packages/concerto-core/lib/introspect/modelfile.js index 56ae29e28..32440d7b8 100644 --- a/packages/concerto-core/lib/introspect/modelfile.js +++ b/packages/concerto-core/lib/introspect/modelfile.js @@ -220,7 +220,6 @@ class ModelFile extends Decorated { */ validate() { super.validate(); - // A dictionary of imports to versions to track unique namespaces const importsMap = new Map(); @@ -756,9 +755,36 @@ class ModelFile extends Decorated { this.importWildcardNamespaces.push(imp.namespace); break; case `${MetaModelNamespace}.ImportTypes`: - imp.types.forEach( type => { - this.importShortNames.set(type, `${imp.namespace}.${type}`); - }); + if (this.getModelManager().isAliasedTypeEnabled()) { + const aliasedTypes = new Map(); + if (imp.aliasedTypes) { + imp.aliasedTypes.forEach(({ name, aliasedName }) => { + if(ModelUtil.isPrimitiveType(aliasedName)){ + throw new Error('Types cannot be aliased to primitive type'); + } + aliasedTypes.set(name, aliasedName); + }); + } + // Local-name(aliased or non-aliased) is mapped to the Fully qualified type name + imp.types.forEach((type) => + aliasedTypes.has(type) + ? this.importShortNames.set( + aliasedTypes.get(type), + `${imp.namespace}.${type}` + ) + : this.importShortNames.set( + type, + `${imp.namespace}.${type}` + ) + ); + } else { + if (imp.aliasedTypes) { + throw new Error('Aliasing disabled, set enableAliasType to true'); + } + imp.types.forEach((type) => { + this.importShortNames.set(type,`${imp.namespace}.${type}`); + }); + } break; default: this.importShortNames.set(imp.name, ModelUtil.importFullyQualifiedNames(imp)[0]); diff --git a/packages/concerto-core/package.json b/packages/concerto-core/package.json index 932fe7abf..ccddce401 100644 --- a/packages/concerto-core/package.json +++ b/packages/concerto-core/package.json @@ -1,6 +1,6 @@ { "name": "@accordproject/concerto-core", - "version": "3.17.0", + "version": "3.17.1", "description": "Core Implementation for the Concerto Modeling Language", "homepage": "https://github.com/accordproject/concerto", "engines": { diff --git a/packages/concerto-core/test/introspect/modelfile.js b/packages/concerto-core/test/introspect/modelfile.js index 16718d3d7..8c6208cb7 100644 --- a/packages/concerto-core/test/introspect/modelfile.js +++ b/packages/concerto-core/test/introspect/modelfile.js @@ -608,6 +608,115 @@ describe('ModelFile', () => { }); + describe('#aliasedImport', () => { + + beforeEach(()=>{ + modelManager.enableAliasedType=true; + }); + it('should resolve aliased name of import type', () => { + const model = ` + namespace org.acme + import org.saluja.{doc as d}`; + + let modelFile = ParserUtil.newModelFile(modelManager, model); + modelFile.resolveImport('d').should.equal('org.saluja.doc'); + }); + + it('should not throw if an aliased import exists for a type that exists in a valid namespace', () => { + const model1 = ` + namespace org.saluja.ext + asset MyAsset2 identified by assetId { + o String assetId + }`; + const model2 = ` + namespace org.acme + import org.saluja.ext.{MyAsset2 as m} + asset MyAsset identified by assetId { + o String assetId + o m[] arr + }`; + let modelFile1 = ParserUtil.newModelFile(modelManager, model1); + modelManager.addModelFile(modelFile1); + let modelFile2 = ParserUtil.newModelFile(modelManager, model2); + (() => modelFile2.validate()).should.not.throw(); + }); + + it('should not throw if an duplicate types aliased to distinct aliased names', () => { + const model1 = ` + namespace org.saluja + asset MyAsset identified by assetId { + o String assetId + }`; + const model2 = ` + namespace org.acme + asset MyAsset identified by assetId { + o String assetId + }`; + + const model3 = ` + namespace org.acme2 + import org.saluja.{MyAsset as m1} + import org.acme.{MyAsset as m2} + + asset MyAsset identified by assetId { + o String assetId + o m1[] arr1 + o m2[] arr2 + }`; + let modelFile1 = ParserUtil.newModelFile(modelManager, model1); + modelManager.addModelFile(modelFile1); + let modelFile2 = ParserUtil.newModelFile(modelManager, model2); + modelManager.addModelFile(modelFile2); + let modelFile3 = ParserUtil.newModelFile(modelManager, model3); + (() => modelFile3.validate()).should.not.throw(); + }); + + it('should not throw if map value is an aliased type', () => { + const model1 = ` + namespace org.saluja + asset Student identified by rollno { + o String rollno + }`; + const model2 = ` + namespace org.acme + import org.saluja.{Student as stud} + + map StudMap{ + o DateTime + o stud + }`; + modelManager.enableMapType=true; + let modelFile1 = ParserUtil.newModelFile(modelManager, model1); + modelManager.addModelFile(modelFile1); + let modelFile2 = ParserUtil.newModelFile(modelManager, model2); + (() => modelFile2.validate()).should.not.throw(); + }); + + it('should not throw if declaration is extended on a aliased type declaration', () => { + const model1 = ` + namespace org.saluja + + scalar nickname extends String + asset Vehicle identified by serialno { + o String serialno + }`; + const model2 = ` + namespace org.acme + import org.saluja.{Vehicle as V,nickname as nk} + + asset Car extends V{ + o String company + o nk shortname + }`; + modelManager.enableMapType = true; + let modelFile1 = ParserUtil.newModelFile(modelManager, model1); + modelManager.addModelFile(modelFile1); + let modelFile2 = ParserUtil.newModelFile(modelManager, model2); + (() => modelFile2.validate()).should.not.throw(); + }); + }); + + describe('#isDefined', () => { let modelManager; diff --git a/packages/concerto-core/types/lib/basemodelmanager.d.ts b/packages/concerto-core/types/lib/basemodelmanager.d.ts index ccf0fdd4e..cf7318ded 100644 --- a/packages/concerto-core/types/lib/basemodelmanager.d.ts +++ b/packages/concerto-core/types/lib/basemodelmanager.d.ts @@ -24,6 +24,7 @@ declare class BaseModelManager { * @param {boolean} [options.metamodelValidation] - When true, modelfiles will be validated * @param {boolean} [options.addMetamodel] - When true, the Concerto metamodel is added to the model manager * @param {boolean} [options.enableMapType] - When true, the Concerto Map Type feature is enabled + * @param {boolean} [options.enableAliasedType] - When true, the Concerto Aliasing feature is enabled * @param {*} [processFile] - how to obtain a concerto AST from an input to the model manager */ constructor(options?: { @@ -32,6 +33,7 @@ declare class BaseModelManager { metamodelValidation?: boolean; addMetamodel?: boolean; enableMapType?: boolean; + enableAliasedType?: boolean; }, processFile?: any); processFile: any; modelFiles: {}; @@ -45,8 +47,10 @@ declare class BaseModelManager { metamodelValidation?: boolean; addMetamodel?: boolean; enableMapType?: boolean; + enableAliasedType?: boolean; }; enableMapType: boolean; + enableAliasedType: boolean; metamodelModelFile: any; /** * Returns true @@ -58,6 +62,11 @@ declare class BaseModelManager { * @returns {boolean} true if the strict has been set */ isStrict(): boolean; + /** + * Returns the value of the enableAliasedType option + * @returns {boolean} true if the enableAliasedType has been set + */ + isAliasedTypeEnabled(): boolean; /** * Adds root types * @private