From caf00773a37dda980a9014695b10c67feda42396 Mon Sep 17 00:00:00 2001 From: jeromesimeon Date: Wed, 4 Aug 2021 11:27:30 -0400 Subject: [PATCH] feature(metamodel) Add support for decorators Signed-off-by: jeromesimeon --- packages/concerto-core/api.txt | 6 + packages/concerto-core/changelog.txt | 2 +- .../concerto-core/lib/introspect/metamodel.js | 123 +++++++++++++++++- .../concerto-core/test/data/model/person.cto | 6 + .../concerto-core/test/data/model/person.json | 84 +++++++++++- .../test/data/model/personResolved.json | 84 +++++++++++- 6 files changed, 300 insertions(+), 5 deletions(-) diff --git a/packages/concerto-core/api.txt b/packages/concerto-core/api.txt index 9790c85142..91574bd2c9 100644 --- a/packages/concerto-core/api.txt +++ b/packages/concerto-core/api.txt @@ -115,6 +115,9 @@ class ModelFileDownloader { + string resolveName() + object resolveTypeNames() + object enumFieldToMetaModel() + + object decoratorArgToMetaModel() + + object decoratorToMetaModel() + + object decoratorsToMetaModel() + object fieldToMetaModel() + object relationshipToMetaModel() + object enumDeclToMetaModel() @@ -122,6 +125,9 @@ class ModelFileDownloader { + object declToMetaModel() + object modelToMetaModel() + object modelFileToMetaModel() + + string decoratorArgFromMetaModel() + + string decoratorFromMetaModel() + + string decoratorsFromMetaModel() + string fieldFromMetaModel() + string declFromMetaModel() + string ctoFromMetaModel() diff --git a/packages/concerto-core/changelog.txt b/packages/concerto-core/changelog.txt index 31da00f99b..281fdc7cb3 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 1.0.5 {1af0901bd065706dc1409d1e3482fa48} 2021-07-13 +Version 1.0.5 {16fb2d5684ec917532a19428c74f1ebf} 2021-07-13 - Add support for Concerto metamodel with import/export to CTO Version 1.0.3 {1fe469fe1a79af5d5a4f5ec7dee6b7d4} 2021-06-25 diff --git a/packages/concerto-core/lib/introspect/metamodel.js b/packages/concerto-core/lib/introspect/metamodel.js index 80a8eedb97..9a5d2fbeef 100644 --- a/packages/concerto-core/lib/introspect/metamodel.js +++ b/packages/concerto-core/lib/introspect/metamodel.js @@ -339,7 +339,6 @@ function resolveTypeNames(metaModel, table) { * @return {object} the metamodel for this field */ function enumFieldToMetaModel(ast) { - // console.log(`FIELD ${JSON.stringify(ast)}`); const field = {}; field.$class = 'concerto.metamodel.EnumFieldDeclaration'; @@ -350,13 +349,68 @@ function enumFieldToMetaModel(ast) { return field; } +/** + * Create metamodel for a decorator argument + * @param {object} ast - the AST for the decorator argument + * @return {object} the metamodel for this decorator argument + */ +function decoratorArgToMetaModel(ast) { + const decoratorArg = {}; + switch (ast.type) { + case 'String': + decoratorArg.$class = 'concerto.metamodel.DecoratorString'; + decoratorArg.value = ast.value; + break; + case 'Number': + decoratorArg.$class = 'concerto.metamodel.DecoratorNumber'; + decoratorArg.value = ast.value; + break; + case 'Boolean': + decoratorArg.$class = 'concerto.metamodel.DecoratorBoolean'; + decoratorArg.value = ast.value; + break; + default: + break; + } + + return decoratorArg; +} + +/** + * Create metamodel for a decorator + * @param {object} ast - the AST for the decorator + * @return {object} the metamodel for this decorator + */ +function decoratorToMetaModel(ast) { + const decorator = { + $class: 'concerto.metamodel.Decorator', + name: ast.name, + }; + if (ast.arguments && ast.arguments.list) { + if (!ast.arguments.list[0]) { + decorator.arguments = []; + } else { + decorator.arguments = ast.arguments.list.map(decoratorArgToMetaModel); + } + } + return decorator; +} + +/** + * Create metamodel for a list of decorators + * @param {object} ast - the AST for the decorators + * @return {object} the metamodel for the decorators + */ +function decoratorsToMetaModel(ast) { + return ast.map(decoratorToMetaModel); +} + /** * Create metamodel for a class field * @param {object} ast - the AST for the field * @return {object} the metamodel for this field */ function fieldToMetaModel(ast) { - // console.log(`FIELD ${JSON.stringify(ast)}`); const field = {}; // Field name @@ -375,6 +429,12 @@ function fieldToMetaModel(ast) { } // XXX Can it be missing? const type = ast.propertyType.name; + + // Handle decorators + if (ast.decorators && ast.decorators.length > 0) { + field.decorators = decoratorsToMetaModel(ast.decorators); + } + switch (type) { case 'Integer': field.$class = 'concerto.metamodel.IntegerFieldDeclaration'; @@ -585,6 +645,11 @@ function classDeclToMetaModel(ast) { } } + // Handle decorators + if (ast.decorators && ast.decorators.length > 0) { + decl.decorators = decoratorsToMetaModel(ast.decorators); + } + // Class fields decl.fields = []; for (let n = 0; n < ast.body.declarations.length; n++) { @@ -677,6 +742,53 @@ function modelFileToMetaModel(modelFile, validate) { return modelToMetaModel(modelFile.ast, validate); } +/** + * Create decorator argument string from a metamodel + * @param {object} mm - the metamodel + * @return {string} the string for the decorator argument + */ +function decoratorArgFromMetaModel(mm) { + let result = ''; + switch (mm.$class) { + case 'concerto.metamodel.DecoratorString': + result += `"${mm.value}"`; + break; + default: + result += `${mm.value}`; + break; + } + return result; +} + +/** + * Create decorator string from a metamodel + * @param {object} mm - the metamodel + * @return {string} the string for the decorator + */ +function decoratorFromMetaModel(mm) { + let result = ''; + result += `@${mm.name}`; + if (mm.arguments) { + result += '('; + result += mm.arguments.map(decoratorArgFromMetaModel).join(','); + result += ')'; + } + return result; +} + +/** + * Create decorators string from a metamodel + * @param {object} mm - the metamodel + * @param {string} prefix - indentation + * @return {string} the string for the decorators + */ +function decoratorsFromMetaModel(mm, prefix) { + let result = ''; + result += mm.map(decoratorFromMetaModel).join(`\n${prefix}`); + result += `\n${prefix}`; + return result; +} + /** * Create a field string from a metamodel * @param {object} mm - the metamodel @@ -687,6 +799,9 @@ function fieldFromMetaModel(mm) { let defaultString = ''; let validatorString = ''; + if (mm.decorators) { + result += decoratorsFromMetaModel(mm.decorators, ' '); + } if (mm.$class === 'concerto.metamodel.RelationshipDeclaration') { result += '-->'; } else { @@ -782,6 +897,10 @@ function fieldFromMetaModel(mm) { */ function declFromMetaModel(mm) { let result = ''; + if (mm.decorators) { + result += decoratorsFromMetaModel(mm.decorators, ''); + } + if (mm.isAbstract) { result += 'abstract '; } diff --git a/packages/concerto-core/test/data/model/person.cto b/packages/concerto-core/test/data/model/person.cto index 3a5ee5e069..c6d47348c6 100644 --- a/packages/concerto-core/test/data/model/person.cto +++ b/packages/concerto-core/test/data/model/person.cto @@ -56,7 +56,13 @@ concept Address identified { o Boolean isPrivate default=false } +@Address("x",1,"y","foo","z",true) +@Address2() +@Address3 concept USAddress extends Address { + @Zip("x",1,"y","foo","z",true) + @Zip2() + @Zip3 o Integer zip4 range=[-365,365] o Integer zip41 range=[,365] o Integer zip42 range=[-365,] diff --git a/packages/concerto-core/test/data/model/person.json b/packages/concerto-core/test/data/model/person.json index 7b4082270c..6d5412d672 100644 --- a/packages/concerto-core/test/data/model/person.json +++ b/packages/concerto-core/test/data/model/person.json @@ -226,6 +226,47 @@ { "$class": "concerto.metamodel.ConceptDeclaration", "name": "USAddress", + "decorators": [ + { + "$class": "concerto.metamodel.Decorator", + "name": "Address", + "arguments": [ + { + "$class": "concerto.metamodel.DecoratorString", + "value": "x" + }, + { + "$class": "concerto.metamodel.DecoratorNumber", + "value": 1 + }, + { + "$class": "concerto.metamodel.DecoratorString", + "value": "y" + }, + { + "$class": "concerto.metamodel.DecoratorString", + "value": "foo" + }, + { + "$class": "concerto.metamodel.DecoratorString", + "value": "z" + }, + { + "$class": "concerto.metamodel.DecoratorBoolean", + "value": true + } + ] + }, + { + "$class": "concerto.metamodel.Decorator", + "name": "Address2", + "arguments": [] + }, + { + "$class": "concerto.metamodel.Decorator", + "name": "Address3" + } + ], "isAbstract": false, "superType": { "$class": "concerto.metamodel.TypeIdentifier", @@ -241,7 +282,48 @@ }, "name": "zip4", "isArray": false, - "isOptional": false + "isOptional": false, + "decorators": [ + { + "$class": "concerto.metamodel.Decorator", + "name": "Zip", + "arguments": [ + { + "$class": "concerto.metamodel.DecoratorString", + "value": "x" + }, + { + "$class": "concerto.metamodel.DecoratorNumber", + "value": 1 + }, + { + "$class": "concerto.metamodel.DecoratorString", + "value": "y" + }, + { + "$class": "concerto.metamodel.DecoratorString", + "value": "foo" + }, + { + "$class": "concerto.metamodel.DecoratorString", + "value": "z" + }, + { + "$class": "concerto.metamodel.DecoratorBoolean", + "value": true + } + ] + }, + { + "$class": "concerto.metamodel.Decorator", + "name": "Zip2", + "arguments": [] + }, + { + "$class": "concerto.metamodel.Decorator", + "name": "Zip3" + } + ] }, { "$class": "concerto.metamodel.IntegerFieldDeclaration", diff --git a/packages/concerto-core/test/data/model/personResolved.json b/packages/concerto-core/test/data/model/personResolved.json index 40d561c928..ca459a99d2 100644 --- a/packages/concerto-core/test/data/model/personResolved.json +++ b/packages/concerto-core/test/data/model/personResolved.json @@ -234,6 +234,47 @@ { "$class": "concerto.metamodel.ConceptDeclaration", "name": "USAddress", + "decorators": [ + { + "$class": "concerto.metamodel.Decorator", + "name": "Address", + "arguments": [ + { + "$class": "concerto.metamodel.DecoratorString", + "value": "x" + }, + { + "$class": "concerto.metamodel.DecoratorNumber", + "value": 1 + }, + { + "$class": "concerto.metamodel.DecoratorString", + "value": "y" + }, + { + "$class": "concerto.metamodel.DecoratorString", + "value": "foo" + }, + { + "$class": "concerto.metamodel.DecoratorString", + "value": "z" + }, + { + "$class": "concerto.metamodel.DecoratorBoolean", + "value": true + } + ] + }, + { + "$class": "concerto.metamodel.Decorator", + "name": "Address2", + "arguments": [] + }, + { + "$class": "concerto.metamodel.Decorator", + "name": "Address3" + } + ], "isAbstract": false, "superType": { "$class": "concerto.metamodel.TypeIdentifier", @@ -250,7 +291,48 @@ }, "name": "zip4", "isArray": false, - "isOptional": false + "isOptional": false, + "decorators": [ + { + "$class": "concerto.metamodel.Decorator", + "name": "Zip", + "arguments": [ + { + "$class": "concerto.metamodel.DecoratorString", + "value": "x" + }, + { + "$class": "concerto.metamodel.DecoratorNumber", + "value": 1 + }, + { + "$class": "concerto.metamodel.DecoratorString", + "value": "y" + }, + { + "$class": "concerto.metamodel.DecoratorString", + "value": "foo" + }, + { + "$class": "concerto.metamodel.DecoratorString", + "value": "z" + }, + { + "$class": "concerto.metamodel.DecoratorBoolean", + "value": true + } + ] + }, + { + "$class": "concerto.metamodel.Decorator", + "name": "Zip2", + "arguments": [] + }, + { + "$class": "concerto.metamodel.Decorator", + "name": "Zip3" + } + ] }, { "$class": "concerto.metamodel.IntegerFieldDeclaration",