From 3d665dc8015bb48330758e98d78ec19aa8372381 Mon Sep 17 00:00:00 2001 From: Sanket Shevkar Date: Mon, 6 Nov 2023 16:43:10 +0530 Subject: [PATCH 1/6] feat(vocabulary): vocaulary for map types Signed-off-by: Sanket Shevkar --- packages/concerto-core/api.txt | 3 + packages/concerto-core/changelog.txt | 3 + .../concerto-core/lib/decoratormanager.js | 6 +- .../lib/introspect/mapkeytype.js | 8 ++ .../lib/introspect/mapvaluetype.js | 8 ++ .../lib/introspect/scalardeclaration.js | 10 ++ .../test/introspect/mapdeclaration.js | 8 ++ .../test/introspect/scalardeclaration.js | 13 +++ .../concerto-vocabulary/lib/vocabulary.js | 35 ++++++- .../lib/vocabularymanager.js | 25 +++-- .../__snapshots__/vocabularymanager.js.snap | 97 +++++++++++++++++++ .../test/org.acme@1.0.0.cto | 7 +- .../test/org.acme@1.0.0_en-gb.voc | 7 +- .../test/org.acme@1.0.0_en.voc | 6 +- .../test/org.acme@1.0.0_fr.voc | 5 +- .../test/vocabularymanager.js | 10 +- 16 files changed, 230 insertions(+), 21 deletions(-) diff --git a/packages/concerto-core/api.txt b/packages/concerto-core/api.txt index c999ea9229..60fafcd40e 100644 --- a/packages/concerto-core/api.txt +++ b/packages/concerto-core/api.txt @@ -180,6 +180,7 @@ class MapKeyType extends Decorated { + String toString() + boolean isKey() + boolean isValue() + + string getNamespace() } class MapValueType extends Decorated { + void constructor(MapDeclaration,Object) throws IllegalModelException @@ -190,6 +191,7 @@ class MapValueType extends Decorated { + String toString() + boolean isKey() + boolean isValue() + + string getNamespace() } + ModelManager newMetaModelManager() + object validateMetaModel() @@ -272,6 +274,7 @@ class ScalarDeclaration extends Declaration { + boolean isTransaction() + boolean isEvent() + boolean isConcept() + + boolean isMapDeclaration() } class TransactionDeclaration extends IdentifiedDeclaration { + void constructor(ModelFile,Object) throws IllegalModelException diff --git a/packages/concerto-core/changelog.txt b/packages/concerto-core/changelog.txt index 6af538a7df..1dd761320f 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.13.2 {8aa6c0e12fe380d694604e3edd965730} 2023-10-18 +- Add getNamespace method to kep type and value type of maps + Version 3.13.1 {f5a9a1ea6a64865843a3abb77798cbb0} 2023-10-18 - Add migrate option to DecoratorManager options diff --git a/packages/concerto-core/lib/decoratormanager.js b/packages/concerto-core/lib/decoratormanager.js index ab684705f8..da6924ca1f 100644 --- a/packages/concerto-core/lib/decoratormanager.js +++ b/packages/concerto-core/lib/decoratormanager.js @@ -54,14 +54,14 @@ enum CommandType { /** * Which models elements to add the decorator to. Any null - * elements are 'wildcards'. + * elements are 'wildcards'. */ concept CommandTarget { o String namespace optional o String declaration optional o String property optional o String[] properties optional // property and properties are mutually exclusive - o String type optional + o String type optional o MapElement mapElement optional } @@ -438,6 +438,8 @@ class DecoratorManager { if (this.falsyOrEqual(target.type, declaration.value.$class)) { this.applyDecorator(declaration.value, type, decorator); } + } else { + this.applyDecorator(declaration, type, decorator); } } else if (!target.property && !target.type) { this.applyDecorator(declaration, type, decorator); diff --git a/packages/concerto-core/lib/introspect/mapkeytype.js b/packages/concerto-core/lib/introspect/mapkeytype.js index acf7ef280a..ee4764142c 100644 --- a/packages/concerto-core/lib/introspect/mapkeytype.js +++ b/packages/concerto-core/lib/introspect/mapkeytype.js @@ -162,6 +162,14 @@ class MapKeyType extends Decorated { isValue() { return false; } + + /** + * Return the namespace of this map key. + * @return {string} namespace - a namespace. + */ + getNamespace() { + return this.modelFile.getNamespace(); + } } module.exports = MapKeyType; diff --git a/packages/concerto-core/lib/introspect/mapvaluetype.js b/packages/concerto-core/lib/introspect/mapvaluetype.js index 9ede733a0b..f5122d8c77 100644 --- a/packages/concerto-core/lib/introspect/mapvaluetype.js +++ b/packages/concerto-core/lib/introspect/mapvaluetype.js @@ -184,6 +184,14 @@ class MapValueType extends Decorated { isValue() { return true; } + + /** + * Return the namespace of this map value. + * @return {string} namespace - a namespace. + */ + getNamespace() { + return this.modelFile.getNamespace(); + } } module.exports = MapValueType; diff --git a/packages/concerto-core/lib/introspect/scalardeclaration.js b/packages/concerto-core/lib/introspect/scalardeclaration.js index 3fab478621..94e4e52eee 100644 --- a/packages/concerto-core/lib/introspect/scalardeclaration.js +++ b/packages/concerto-core/lib/introspect/scalardeclaration.js @@ -278,6 +278,16 @@ class ScalarDeclaration extends Declaration { return false; } + /** + * Returns true if this class is the definition of a map-declaration. + * + * @return {boolean} true if the class is a map-declaration + * @deprecated + */ + isMapDeclaration() { + return false; + } + } module.exports = ScalarDeclaration; diff --git a/packages/concerto-core/test/introspect/mapdeclaration.js b/packages/concerto-core/test/introspect/mapdeclaration.js index c5e753f353..7409838e68 100644 --- a/packages/concerto-core/test/introspect/mapdeclaration.js +++ b/packages/concerto-core/test/introspect/mapdeclaration.js @@ -750,4 +750,12 @@ describe('MapDeclaration', () => { declaration.getValue().getParent().should.equal(declaration); }); }); + + describe('#getNamespace', () => { + it('should return the correct namespace MapDeclaration Key and Value ', () => { + let declaration = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.primitive.string.cto', MapDeclaration); + declaration.getKey().getNamespace().should.equal('com.acme@1.0.0'); + declaration.getValue().getNamespace().should.equal('com.acme@1.0.0'); + }); + }); }); diff --git a/packages/concerto-core/test/introspect/scalardeclaration.js b/packages/concerto-core/test/introspect/scalardeclaration.js index 2e05ff0ede..603de9bd4d 100644 --- a/packages/concerto-core/test/introspect/scalardeclaration.js +++ b/packages/concerto-core/test/introspect/scalardeclaration.js @@ -305,4 +305,17 @@ describe('ScalarDeclaration', () => { testClass.isClassDeclaration().should.be.false; }); }); + + describe('#isMapDeclaration', () => { + const modelFileName = 'test/data/parser/scalardeclaration.ssn.cto'; + + beforeEach(() => { + const modelFiles = introspectUtils.loadModelFiles([modelFileName], modelManager); + modelManager.addModelFiles(modelFiles); + }); + it('should return false', () => { + const testClass = modelManager.getType('com.testing.SSN'); + testClass.isMapDeclaration().should.be.false; + }); + }); }); diff --git a/packages/concerto-vocabulary/lib/vocabulary.js b/packages/concerto-vocabulary/lib/vocabulary.js index 7ac709b25b..4d3a6a3bf1 100644 --- a/packages/concerto-vocabulary/lib/vocabulary.js +++ b/packages/concerto-vocabulary/lib/vocabulary.js @@ -145,15 +145,42 @@ class Vocabulary { */ validate(modelFile) { const getOwnProperties = (d) => { - // ensures we have a valid return, even for scalars - return d.getOwnProperties?.() ? d.getOwnProperties?.() : []; + // ensures we have a valid return, even for scalars and map-declarations + return d.isMapDeclaration() ? [d.getKey(), d.getValue()] : d.getOwnProperties?.() ? d.getOwnProperties?.() : []; + }; + + const getPropertyName = (p) => { + if(p.isKey?.()) { + return 'KEY'; + } else if(p.isValue?.()) { + return 'VALUE'; + } else { + return p.getName(); + } + }; + + const checkProperties = (k, p) => { + const declaration = modelFile.getLocalType(Object.keys(k)[0]); + const property = Object.keys(p)[0]; + if(declaration.isMapDeclaration()) { + if (!property.localeCompare('KEY')) { + return true; + } else if(!property.localeCompare('VALUE')) { + return true; + } else { + return false; + } + } else { + return declaration.getOwnProperty(Object.keys(p)[0]); + } + }; const result = { missingTerms: modelFile.getAllDeclarations().flatMap( d => this.getTerm(d.getName()) - ? getOwnProperties(d).flatMap( p => this.getTerm(d.getName(), p.getName()) ? null : `${d.getName()}.${p.getName()}`) + ? getOwnProperties(d).flatMap( p => this.getTerm(d.getName(), getPropertyName(p)) ? null : `${d.getName()}.${getPropertyName(p)}`) : d.getName() ).filter( i => i !== null), additionalTerms: this.content.declarations.flatMap( k => modelFile.getLocalType(Object.keys(k)[0]) - ? Array.isArray(k.properties) ? k.properties.flatMap( p => modelFile.getLocalType(Object.keys(k)[0]).getOwnProperty(Object.keys(p)[0]) ? null : `${Object.keys(k)[0]}.${Object.keys(p)[0]}`) : null + ? Array.isArray(k.properties) ? k.properties.flatMap( p => checkProperties(k, p) ? null : `${Object.keys(k)[0]}.${Object.keys(p)[0]}`) : null : k ).filter( i => i !== null) }; diff --git a/packages/concerto-vocabulary/lib/vocabularymanager.js b/packages/concerto-vocabulary/lib/vocabularymanager.js index de15b18133..eb6449b36e 100644 --- a/packages/concerto-vocabulary/lib/vocabularymanager.js +++ b/packages/concerto-vocabulary/lib/vocabularymanager.js @@ -205,7 +205,18 @@ class VocabularyManager { resolveTerms(modelManager, namespace, locale, declarationName, propertyName) { const modelFile = modelManager.getModelFile(namespace); const classDecl = modelFile ? modelFile.getType(declarationName) : null; - const property = propertyName ? classDecl ? classDecl.getProperty(propertyName) : null : null; + let property; + if(classDecl && !classDecl.isScalarDeclaration()) { + if(classDecl.isMapDeclaration()) { + if(propertyName === 'KEY') { + property = classDecl.getKey(); + } else if(propertyName === 'VALUE') { + property = classDecl.getValue(); + } + } else { + property = propertyName ? classDecl ? classDecl.getProperty(propertyName) : null : null; + } + } return this.getTerms(property ? property.getNamespace() : namespace, locale, property ? property.getParent().getName() : declarationName, propertyName); } @@ -336,11 +347,13 @@ class VocabularyManager { }); } - decl.getProperties?.().forEach(property => { - const propertyTerms = this.resolveTerms(modelManager, model.getNamespace(), locale, decl.getName(), property.getName()); + const propertyNames = decl.getProperties ? decl.getProperties().map(property => property.getName()) : decl.isMapDeclaration ? decl.isMapDeclaration() ? ['KEY', 'VALUE'] : [] : []; + propertyNames.forEach(propertyName => { + const propertyTerms = this.resolveTerms(modelManager, model.getNamespace(), locale, decl.getName(), propertyName); if (propertyTerms) { Object.keys(propertyTerms).forEach( term => { - if(term === property.getName()) { + const propertyType = !propertyName.localeCompare('KEY') || !propertyName.localeCompare('VALUE') ? 'mapElement' : 'property'; + if(term === propertyName) { decoratorCommandSet.commands.push({ '$class': `${DC_NAMESPACE}.Command`, 'type': 'UPSERT', @@ -348,7 +361,7 @@ class VocabularyManager { '$class': `${DC_NAMESPACE}.CommandTarget`, 'namespace': model.getNamespace(), 'declaration': decl.getName(), - 'property': property.getName() + [propertyType]: propertyName }, 'decorator': { '$class': `${MetaModelNamespace}.Decorator`, @@ -370,7 +383,7 @@ class VocabularyManager { '$class': `${DC_NAMESPACE}.CommandTarget`, 'namespace': model.getNamespace(), 'declaration': decl.getName(), - 'property': property.getName() + [propertyType]: propertyName }, 'decorator': { '$class': `${MetaModelNamespace}.Decorator`, diff --git a/packages/concerto-vocabulary/test/__snapshots__/vocabularymanager.js.snap b/packages/concerto-vocabulary/test/__snapshots__/vocabularymanager.js.snap index 21f3415664..672c4137ee 100644 --- a/packages/concerto-vocabulary/test/__snapshots__/vocabularymanager.js.snap +++ b/packages/concerto-vocabulary/test/__snapshots__/vocabularymanager.js.snap @@ -220,6 +220,103 @@ Object { }, "type": "UPSERT", }, + Object { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "decorator": Object { + "$class": "concerto.metamodel@1.0.0.Decorator", + "arguments": Array [ + Object { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "Address of the vehicle", + }, + ], + "name": "Term", + }, + "target": Object { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "declaration": "Address", + "namespace": "org.acme@1.0.0", + }, + "type": "UPSERT", + }, + Object { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "decorator": Object { + "$class": "concerto.metamodel@1.0.0.Decorator", + "arguments": Array [ + Object { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "Registered address of the vehicle owner", + }, + ], + "name": "Term_description", + }, + "target": Object { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "declaration": "Address", + "namespace": "org.acme@1.0.0", + }, + "type": "UPSERT", + }, + Object { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "decorator": Object { + "$class": "concerto.metamodel@1.0.0.Decorator", + "arguments": Array [ + Object { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "View Address of vehicle owner", + }, + ], + "name": "Term_tooltip", + }, + "target": Object { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "declaration": "Address", + "namespace": "org.acme@1.0.0", + }, + "type": "UPSERT", + }, + Object { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "decorator": Object { + "$class": "concerto.metamodel@1.0.0.Decorator", + "arguments": Array [ + Object { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "vin of the vehicle", + }, + ], + "name": "Term", + }, + "target": Object { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "declaration": "Address", + "mapElement": "KEY", + "namespace": "org.acme@1.0.0", + }, + "type": "UPSERT", + }, + Object { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "decorator": Object { + "$class": "concerto.metamodel@1.0.0.Decorator", + "arguments": Array [ + Object { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "address of the vehicle", + }, + ], + "name": "Term", + }, + "target": Object { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "declaration": "Address", + "mapElement": "VALUE", + "namespace": "org.acme@1.0.0", + }, + "type": "UPSERT", + }, Object { "$class": "org.accordproject.decoratorcommands@0.3.0.Command", "decorator": Object { diff --git a/packages/concerto-vocabulary/test/org.acme@1.0.0.cto b/packages/concerto-vocabulary/test/org.acme@1.0.0.cto index 40958bdae6..504a7e1b1d 100644 --- a/packages/concerto-vocabulary/test/org.acme@1.0.0.cto +++ b/packages/concerto-vocabulary/test/org.acme@1.0.0.cto @@ -14,6 +14,11 @@ asset Vehicle identified by vin { o Color color } +map Address { + o String + o String +} + asset Truck extends Vehicle { o Double weight -} \ No newline at end of file +} diff --git a/packages/concerto-vocabulary/test/org.acme@1.0.0_en-gb.voc b/packages/concerto-vocabulary/test/org.acme@1.0.0_en-gb.voc index 40ab77711f..4737985310 100644 --- a/packages/concerto-vocabulary/test/org.acme@1.0.0_en-gb.voc +++ b/packages/concerto-vocabulary/test/org.acme@1.0.0_en-gb.voc @@ -4,4 +4,9 @@ declarations: - Truck: A lorry description: A heavy goods vehicle - Color: A colour - - Milkfloat \ No newline at end of file + - Milkfloat + - Address: Address of the vehicle + description: Registered address of the vehicle owner + tooltip: View Address of vehicle owner + properties: + - keyy: mispelled KEY spelling diff --git a/packages/concerto-vocabulary/test/org.acme@1.0.0_en.voc b/packages/concerto-vocabulary/test/org.acme@1.0.0_en.voc index 2b890ec3a7..d051e17221 100644 --- a/packages/concerto-vocabulary/test/org.acme@1.0.0_en.voc +++ b/packages/concerto-vocabulary/test/org.acme@1.0.0_en.voc @@ -17,4 +17,8 @@ declarations: tooltip: Truck weight - horsePower: The horse power of the truck description: The horse power of the truck - tooltip: Truck HP \ No newline at end of file + tooltip: Truck HP + - Address: Registered address of the vehicle + properties: + - KEY: vin of the vehicle + - VALUE: address of the vehicle diff --git a/packages/concerto-vocabulary/test/org.acme@1.0.0_fr.voc b/packages/concerto-vocabulary/test/org.acme@1.0.0_fr.voc index 09123f6e46..d0079fcbc2 100644 --- a/packages/concerto-vocabulary/test/org.acme@1.0.0_fr.voc +++ b/packages/concerto-vocabulary/test/org.acme@1.0.0_fr.voc @@ -3,4 +3,7 @@ namespace: org.acme@1.0.0 declarations: - Vehicle: Véhicule properties: - - vin: Le numéro d'identification du véhicule (NIV) \ No newline at end of file + - vin: Le numéro d'identification du véhicule (NIV) + - Address: Adresse + properties: + - KEY: NIV du véhicule diff --git a/packages/concerto-vocabulary/test/vocabularymanager.js b/packages/concerto-vocabulary/test/vocabularymanager.js index fcc30385bc..8af0ebea42 100644 --- a/packages/concerto-vocabulary/test/vocabularymanager.js +++ b/packages/concerto-vocabulary/test/vocabularymanager.js @@ -127,14 +127,14 @@ describe('VocabularyManager', () => { const voc = vocabularyManager.getVocabulary('org.acme@1.0.0', 'en'); voc.should.not.be.null; const terms = voc.getTerms(); - terms.length.should.equal(4); + terms.length.should.equal(5); }); it('getTerms - fr', () => { const voc = vocabularyManager.getVocabulary('org.acme@1.0.0', 'fr'); voc.should.not.be.null; const terms = voc.getTerms(); - terms.length.should.equal(1); + terms.length.should.equal(2); }); it('getTerms - lookup declaration', () => { @@ -367,10 +367,10 @@ describe('VocabularyManager', () => { result.additionalVocabularies[0].getNamespace().should.equal('com.example@1.0.0'); result.vocabularies['org.acme@1.0.0/en'].additionalTerms.should.have.members(['Vehicle.model', 'Truck.horsePower']); result.vocabularies['org.acme@1.0.0/en'].missingTerms.should.have.members(['Color.RED', 'Color.BLUE', 'Color.GREEN', 'SSN', 'Vehicle.color']); - result.vocabularies['org.acme@1.0.0/en-gb'].additionalTerms.should.have.members(['Milkfloat']); - result.vocabularies['org.acme@1.0.0/fr'].missingTerms.should.have.members(['Color', 'SSN', 'VIN', 'Vehicle.color', 'Truck']); + result.vocabularies['org.acme@1.0.0/en-gb'].additionalTerms.should.have.members(['Milkfloat', 'Address.keyy']); + result.vocabularies['org.acme@1.0.0/fr'].missingTerms.should.have.members(['Color', 'SSN', 'VIN', 'Vehicle.color', 'Address.VALUE', 'Truck']); result.vocabularies['org.acme@1.0.0/fr'].additionalTerms.should.have.members([]); - result.vocabularies['org.acme@1.0.0/zh-cn'].missingTerms.should.have.members(['SSN', 'VIN', 'Truck']); + result.vocabularies['org.acme@1.0.0/zh-cn'].missingTerms.should.have.members(['SSN', 'VIN', 'Address', 'Truck']); result.vocabularies['org.acme@1.0.0/zh-cn'].additionalTerms.should.have.members([]); }); From 32dbe872ccda71280d05f131a319f9c0d6f3ec10 Mon Sep 17 00:00:00 2001 From: Sanket Shevkar Date: Mon, 6 Nov 2023 17:05:34 +0530 Subject: [PATCH 2/6] feat(vocabulary): enable map types for tests Signed-off-by: Sanket Shevkar --- packages/concerto-vocabulary/test/vocabularymanager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/concerto-vocabulary/test/vocabularymanager.js b/packages/concerto-vocabulary/test/vocabularymanager.js index 8af0ebea42..18e5ca2991 100644 --- a/packages/concerto-vocabulary/test/vocabularymanager.js +++ b/packages/concerto-vocabulary/test/vocabularymanager.js @@ -34,7 +34,7 @@ let vocabularyManager = null; describe('VocabularyManager', () => { beforeEach(() => { - modelManager = new ModelManager(); + modelManager = new ModelManager({ enableMapType: true}); const model = fs.readFileSync('./test/org.acme@1.0.0.cto', 'utf-8'); modelManager.addCTOModel(model); const model2 = fs.readFileSync('./test/org.accordproject@1.0.0.cto', 'utf-8'); From 5a9742b62861fb867ef862e717a351530e5f6c40 Mon Sep 17 00:00:00 2001 From: Sanket Shevkar Date: Tue, 7 Nov 2023 18:10:34 +0530 Subject: [PATCH 3/6] feat(vocabulary): resolved review comments Signed-off-by: Sanket Shevkar --- .../concerto-vocabulary/lib/vocabulary.js | 26 +++++++++++-------- .../lib/vocabularymanager.js | 14 ++++++++-- .../test/vocabularymanager.js | 3 ++- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/packages/concerto-vocabulary/lib/vocabulary.js b/packages/concerto-vocabulary/lib/vocabulary.js index 4d3a6a3bf1..6a446b48c5 100644 --- a/packages/concerto-vocabulary/lib/vocabulary.js +++ b/packages/concerto-vocabulary/lib/vocabulary.js @@ -144,28 +144,32 @@ class Vocabulary { * @returns {*} an object with missingTerms and additionalTerms properties */ validate(modelFile) { - const getOwnProperties = (d) => { + const getOwnProperties = (declaration) => { // ensures we have a valid return, even for scalars and map-declarations - return d.isMapDeclaration() ? [d.getKey(), d.getValue()] : d.getOwnProperties?.() ? d.getOwnProperties?.() : []; + if(declaration.isMapDeclaration()) { + return [declaration.getKey(), declaration.getValue()]; + } else { + return declaration.getOwnProperties?.() ? declaration.getOwnProperties?.() : []; + } }; - const getPropertyName = (p) => { - if(p.isKey?.()) { + const getPropertyName = (property) => { + if(property.isKey?.()) { return 'KEY'; - } else if(p.isValue?.()) { + } else if(property.isValue?.()) { return 'VALUE'; } else { - return p.getName(); + return property.getName(); } }; - const checkProperties = (k, p) => { + const checkPropertyExists = (k, p) => { const declaration = modelFile.getLocalType(Object.keys(k)[0]); const property = Object.keys(p)[0]; if(declaration.isMapDeclaration()) { - if (!property.localeCompare('KEY')) { + if (property === 'KEY') { return true; - } else if(!property.localeCompare('VALUE')) { + } else if(property === 'VALUE') { return true; } else { return false; @@ -173,14 +177,14 @@ class Vocabulary { } else { return declaration.getOwnProperty(Object.keys(p)[0]); } - }; + const result = { missingTerms: modelFile.getAllDeclarations().flatMap( d => this.getTerm(d.getName()) ? getOwnProperties(d).flatMap( p => this.getTerm(d.getName(), getPropertyName(p)) ? null : `${d.getName()}.${getPropertyName(p)}`) : d.getName() ).filter( i => i !== null), additionalTerms: this.content.declarations.flatMap( k => modelFile.getLocalType(Object.keys(k)[0]) - ? Array.isArray(k.properties) ? k.properties.flatMap( p => checkProperties(k, p) ? null : `${Object.keys(k)[0]}.${Object.keys(p)[0]}`) : null + ? Array.isArray(k.properties) ? k.properties.flatMap( p => checkPropertyExists(k, p) ? null : `${Object.keys(k)[0]}.${Object.keys(p)[0]}`) : null : k ).filter( i => i !== null) }; diff --git a/packages/concerto-vocabulary/lib/vocabularymanager.js b/packages/concerto-vocabulary/lib/vocabularymanager.js index eb6449b36e..7bc650e096 100644 --- a/packages/concerto-vocabulary/lib/vocabularymanager.js +++ b/packages/concerto-vocabulary/lib/vocabularymanager.js @@ -297,6 +297,16 @@ class VocabularyManager { 'commands': [] }; + const getPropertyNames = (declaration) => { + if (declaration.getProperties) { + return declaration.getProperties().map(property => property.getName()); + } else if(declaration.isMapDeclaration?.()) { + return ['KEY', 'VALUE']; + } else { + return []; + } + }; + modelManager.getModelFiles().forEach(model => { model.getAllDeclarations().forEach(decl => { const terms = this.resolveTerms(modelManager, model.getNamespace(), locale, decl.getName()); @@ -347,12 +357,12 @@ class VocabularyManager { }); } - const propertyNames = decl.getProperties ? decl.getProperties().map(property => property.getName()) : decl.isMapDeclaration ? decl.isMapDeclaration() ? ['KEY', 'VALUE'] : [] : []; + const propertyNames = getPropertyNames(decl); propertyNames.forEach(propertyName => { const propertyTerms = this.resolveTerms(modelManager, model.getNamespace(), locale, decl.getName(), propertyName); if (propertyTerms) { Object.keys(propertyTerms).forEach( term => { - const propertyType = !propertyName.localeCompare('KEY') || !propertyName.localeCompare('VALUE') ? 'mapElement' : 'property'; + const propertyType = propertyName === 'KEY' || propertyName === 'VALUE' ? 'mapElement' : 'property'; if(term === propertyName) { decoratorCommandSet.commands.push({ '$class': `${DC_NAMESPACE}.Command`, diff --git a/packages/concerto-vocabulary/test/vocabularymanager.js b/packages/concerto-vocabulary/test/vocabularymanager.js index 18e5ca2991..3505cdd6d5 100644 --- a/packages/concerto-vocabulary/test/vocabularymanager.js +++ b/packages/concerto-vocabulary/test/vocabularymanager.js @@ -34,7 +34,8 @@ let vocabularyManager = null; describe('VocabularyManager', () => { beforeEach(() => { - modelManager = new ModelManager({ enableMapType: true}); + process.env.ENABLE_MAP_TYPE = 'true'; // TODO Remove on release of MapType + modelManager = new ModelManager(); const model = fs.readFileSync('./test/org.acme@1.0.0.cto', 'utf-8'); modelManager.addCTOModel(model); const model2 = fs.readFileSync('./test/org.accordproject@1.0.0.cto', 'utf-8'); From 9344c227436e90356ffef279bc7f5c7ec7f013fb Mon Sep 17 00:00:00 2001 From: Sanket Shevkar Date: Wed, 8 Nov 2023 16:07:59 +0530 Subject: [PATCH 4/6] feat(vocabulary): addressed comments Signed-off-by: Sanket Shevkar --- packages/concerto-core/changelog.txt | 2 +- .../decoratorcommands/map-declaration.json | 14 ++++++++++++++ .../concerto-core/test/decoratormanager.js | 18 +++++++++++++++++- .../test/introspect/mapdeclaration.js | 2 +- .../test/org.acme@1.0.0_en-gb.voc | 2 +- .../test/vocabularymanager.js | 2 +- 6 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/concerto-core/changelog.txt b/packages/concerto-core/changelog.txt index 1dd761320f..05977f5f5b 100644 --- a/packages/concerto-core/changelog.txt +++ b/packages/concerto-core/changelog.txt @@ -25,7 +25,7 @@ # Version 3.13.2 {8aa6c0e12fe380d694604e3edd965730} 2023-10-18 -- Add getNamespace method to kep type and value type of maps +- Add getNamespace method to key type and value type of maps Version 3.13.1 {f5a9a1ea6a64865843a3abb77798cbb0} 2023-10-18 - Add migrate option to DecoratorManager options diff --git a/packages/concerto-core/test/data/decoratorcommands/map-declaration.json b/packages/concerto-core/test/data/decoratorcommands/map-declaration.json index 92db66dd9b..9c4dfa25fc 100644 --- a/packages/concerto-core/test/data/decoratorcommands/map-declaration.json +++ b/packages/concerto-core/test/data/decoratorcommands/map-declaration.json @@ -3,6 +3,20 @@ "name" : "web", "version": "1.0.0", "commands" : [ + { + "$class" : "org.accordproject.decoratorcommands@0.3.0.Command", + "type" : "UPSERT", + "target" : { + "$class" : "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace" : "test@1.0.0", + "declaration" : "Dictionary" + }, + "decorator" : { + "$class" : "concerto.metamodel@1.0.0.Decorator", + "name" : "MapDeclarationDecorator", + "arguments" : [] + } + }, { "$class" : "org.accordproject.decoratorcommands@0.3.0.Command", "type" : "APPEND", diff --git a/packages/concerto-core/test/decoratormanager.js b/packages/concerto-core/test/decoratormanager.js index 4003aee13f..170c1f90cd 100644 --- a/packages/concerto-core/test/decoratormanager.js +++ b/packages/concerto-core/test/decoratormanager.js @@ -188,6 +188,21 @@ describe('DecoratorManager', () => { decoratorCity2Property.should.not.be.null; }); + it('should decorate the specified element on the specified Map Declaration (Map Declaration)', async function() { + // load a model to decorate + const testModelManager = new ModelManager({strict:true, skipLocationNodes: true}); + const modelText = fs.readFileSync('./test/data/decoratorcommands/test.cto', 'utf-8'); + testModelManager.addCTOModel(modelText, 'test.cto'); + + const dcs = fs.readFileSync('./test/data/decoratorcommands/map-declaration.json', 'utf-8'); + const decoratedModelManager = DecoratorManager.decorateModels( testModelManager, JSON.parse(dcs), + {validate: true, validateCommands: true}); + + const dictionary = decoratedModelManager.getType('test@1.0.0.Dictionary'); + dictionary.should.not.be.null; + dictionary.getDecorator('MapDeclarationDecorator').should.not.be.null; + }); + it('should decorate the specified element on the specified Map Declaration (Map Key)', async function() { // load a model to decorate const testModelManager = new ModelManager({strict:true, skipLocationNodes: true}); @@ -284,7 +299,7 @@ describe('DecoratorManager', () => { dictionary.value.getDecorator('DecoratesValueByType').should.not.be.null; }); - it('should decorate both Key and Value elements on the specified Map Declaration', async function() { + it('should decorate Declaration, Key and Value elements on the specified Map Declaration', async function() { // load a model to decorate const testModelManager = new ModelManager({strict:true, skipLocationNodes: true}); const modelText = fs.readFileSync('./test/data/decoratorcommands/test.cto', 'utf-8'); @@ -297,6 +312,7 @@ describe('DecoratorManager', () => { const dictionary = decoratedModelManager.getType('test@1.0.0.Dictionary'); dictionary.should.not.be.null; + dictionary.getDecorator('MapDeclarationDecorator').should.not.be.null; dictionary.key.getDecorator('Baz').should.not.be.null; dictionary.value.getDecorator('Baz').should.not.be.null; }); diff --git a/packages/concerto-core/test/introspect/mapdeclaration.js b/packages/concerto-core/test/introspect/mapdeclaration.js index 7409838e68..494f64540f 100644 --- a/packages/concerto-core/test/introspect/mapdeclaration.js +++ b/packages/concerto-core/test/introspect/mapdeclaration.js @@ -752,7 +752,7 @@ describe('MapDeclaration', () => { }); describe('#getNamespace', () => { - it('should return the correct namespace MapDeclaration Key and Value ', () => { + it('should return the correct namespace for a Map Declaration Key and Value', () => { let declaration = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.primitive.string.cto', MapDeclaration); declaration.getKey().getNamespace().should.equal('com.acme@1.0.0'); declaration.getValue().getNamespace().should.equal('com.acme@1.0.0'); diff --git a/packages/concerto-vocabulary/test/org.acme@1.0.0_en-gb.voc b/packages/concerto-vocabulary/test/org.acme@1.0.0_en-gb.voc index 4737985310..b380a0ff01 100644 --- a/packages/concerto-vocabulary/test/org.acme@1.0.0_en-gb.voc +++ b/packages/concerto-vocabulary/test/org.acme@1.0.0_en-gb.voc @@ -9,4 +9,4 @@ declarations: description: Registered address of the vehicle owner tooltip: View Address of vehicle owner properties: - - keyy: mispelled KEY spelling + - BAD_KEY: mispelled KEY spelling diff --git a/packages/concerto-vocabulary/test/vocabularymanager.js b/packages/concerto-vocabulary/test/vocabularymanager.js index 3505cdd6d5..520cd22ca4 100644 --- a/packages/concerto-vocabulary/test/vocabularymanager.js +++ b/packages/concerto-vocabulary/test/vocabularymanager.js @@ -368,7 +368,7 @@ describe('VocabularyManager', () => { result.additionalVocabularies[0].getNamespace().should.equal('com.example@1.0.0'); result.vocabularies['org.acme@1.0.0/en'].additionalTerms.should.have.members(['Vehicle.model', 'Truck.horsePower']); result.vocabularies['org.acme@1.0.0/en'].missingTerms.should.have.members(['Color.RED', 'Color.BLUE', 'Color.GREEN', 'SSN', 'Vehicle.color']); - result.vocabularies['org.acme@1.0.0/en-gb'].additionalTerms.should.have.members(['Milkfloat', 'Address.keyy']); + result.vocabularies['org.acme@1.0.0/en-gb'].additionalTerms.should.have.members(['Milkfloat', 'Address.BAD_KEY']); result.vocabularies['org.acme@1.0.0/fr'].missingTerms.should.have.members(['Color', 'SSN', 'VIN', 'Vehicle.color', 'Address.VALUE', 'Truck']); result.vocabularies['org.acme@1.0.0/fr'].additionalTerms.should.have.members([]); result.vocabularies['org.acme@1.0.0/zh-cn'].missingTerms.should.have.members(['SSN', 'VIN', 'Address', 'Truck']); From a0c7081923e2a828e678bbb482809d29971d11fb Mon Sep 17 00:00:00 2001 From: Sanket Shevkar Date: Thu, 9 Nov 2023 12:32:34 +0530 Subject: [PATCH 5/6] feat(vocabulary): addressed comments Signed-off-by: Sanket Shevkar --- packages/concerto-core/test/decoratormanager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/concerto-core/test/decoratormanager.js b/packages/concerto-core/test/decoratormanager.js index 170c1f90cd..6ef3fa6a7c 100644 --- a/packages/concerto-core/test/decoratormanager.js +++ b/packages/concerto-core/test/decoratormanager.js @@ -188,7 +188,7 @@ describe('DecoratorManager', () => { decoratorCity2Property.should.not.be.null; }); - it('should decorate the specified element on the specified Map Declaration (Map Declaration)', async function() { + it('should decorate the specified MapDeclaration', async function() { // load a model to decorate const testModelManager = new ModelManager({strict:true, skipLocationNodes: true}); const modelText = fs.readFileSync('./test/data/decoratorcommands/test.cto', 'utf-8'); From 00ba0c08631def90137a6bba9e8382fc27d82ead Mon Sep 17 00:00:00 2001 From: Sanket Shevkar Date: Fri, 10 Nov 2023 15:54:38 +0530 Subject: [PATCH 6/6] feat(vocabulary): addressed comments Signed-off-by: Sanket Shevkar --- packages/concerto-core/api.txt | 2 +- packages/concerto-core/changelog.txt | 2 +- .../concerto-core/lib/introspect/declaration.js | 9 +++++++++ .../lib/introspect/scalardeclaration.js | 10 ---------- .../concerto-core/test/introspect/declaration.js | 6 ++++++ .../test/introspect/scalardeclaration.js | 13 ------------- 6 files changed, 17 insertions(+), 25 deletions(-) diff --git a/packages/concerto-core/api.txt b/packages/concerto-core/api.txt index 60fafcd40e..23be2927ed 100644 --- a/packages/concerto-core/api.txt +++ b/packages/concerto-core/api.txt @@ -124,6 +124,7 @@ class Declaration extends Decorated { + boolean isEnum() + boolean isClassDeclaration() + boolean isScalarDeclaration() + + boolean isMapDeclaration() } class Decorator { + void constructor(ClassDeclaration|Property,Object) throws IllegalModelException @@ -274,7 +275,6 @@ class ScalarDeclaration extends Declaration { + boolean isTransaction() + boolean isEvent() + boolean isConcept() - + boolean isMapDeclaration() } class TransactionDeclaration extends IdentifiedDeclaration { + void constructor(ModelFile,Object) throws IllegalModelException diff --git a/packages/concerto-core/changelog.txt b/packages/concerto-core/changelog.txt index 05977f5f5b..66500462d6 100644 --- a/packages/concerto-core/changelog.txt +++ b/packages/concerto-core/changelog.txt @@ -24,7 +24,7 @@ # Note that the latest public API is documented using JSDocs and is available in api.txt. # -Version 3.13.2 {8aa6c0e12fe380d694604e3edd965730} 2023-10-18 +Version 3.13.2 {dccc690753912cf87e7ceec56d949058} 2023-10-18 - Add getNamespace method to key type and value type of maps Version 3.13.1 {f5a9a1ea6a64865843a3abb77798cbb0} 2023-10-18 diff --git a/packages/concerto-core/lib/introspect/declaration.js b/packages/concerto-core/lib/introspect/declaration.js index 0bd8b59e5c..033c2afe45 100644 --- a/packages/concerto-core/lib/introspect/declaration.js +++ b/packages/concerto-core/lib/introspect/declaration.js @@ -177,6 +177,15 @@ class Declaration extends Decorated { isScalarDeclaration() { return false; } + + /** + * Returns true if this class is the definition of a map-declaration. + * + * @return {boolean} true if the class is a map-declaration + */ + isMapDeclaration() { + return false; + } } module.exports = Declaration; diff --git a/packages/concerto-core/lib/introspect/scalardeclaration.js b/packages/concerto-core/lib/introspect/scalardeclaration.js index 94e4e52eee..3fab478621 100644 --- a/packages/concerto-core/lib/introspect/scalardeclaration.js +++ b/packages/concerto-core/lib/introspect/scalardeclaration.js @@ -278,16 +278,6 @@ class ScalarDeclaration extends Declaration { return false; } - /** - * Returns true if this class is the definition of a map-declaration. - * - * @return {boolean} true if the class is a map-declaration - * @deprecated - */ - isMapDeclaration() { - return false; - } - } module.exports = ScalarDeclaration; diff --git a/packages/concerto-core/test/introspect/declaration.js b/packages/concerto-core/test/introspect/declaration.js index 90c5f1f256..df712bac00 100644 --- a/packages/concerto-core/test/introspect/declaration.js +++ b/packages/concerto-core/test/introspect/declaration.js @@ -47,6 +47,12 @@ describe('Declaration', () => { }); }); + describe('#isMapDeclaration', () => { + it('should be false', () => { + declaration.isMapDeclaration().should.equal(false); + }); + }); + describe('#isSystemIdentified', () => { it('should be false', () => { declaration.isSystemIdentified().should.equal(false); diff --git a/packages/concerto-core/test/introspect/scalardeclaration.js b/packages/concerto-core/test/introspect/scalardeclaration.js index 603de9bd4d..2e05ff0ede 100644 --- a/packages/concerto-core/test/introspect/scalardeclaration.js +++ b/packages/concerto-core/test/introspect/scalardeclaration.js @@ -305,17 +305,4 @@ describe('ScalarDeclaration', () => { testClass.isClassDeclaration().should.be.false; }); }); - - describe('#isMapDeclaration', () => { - const modelFileName = 'test/data/parser/scalardeclaration.ssn.cto'; - - beforeEach(() => { - const modelFiles = introspectUtils.loadModelFiles([modelFileName], modelManager); - modelManager.addModelFiles(modelFiles); - }); - it('should return false', () => { - const testClass = modelManager.getType('com.testing.SSN'); - testClass.isMapDeclaration().should.be.false; - }); - }); });