diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 927c0354fd..16bb583717 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -6,7 +6,6 @@ The maintainers are listed in alphabetical order. - Matt Roberts ([mttrbrts](https://github.com/mttrbrts)) - Daniel Selman ([dselman](https://github.com/dselman)) -- Jerome Simeon ([jeromesimeon](https://github.com/jeromesimeon)) ## License Accord Project Project source code files are made available under the Apache License, Version 2.0 (Apache-2.0), located in the LICENSE file. Hyperledger Project documentation files are made available under the Creative Commons Attribution 4.0 International License (CC-BY-4.0), available at http://creativecommons.org/licenses/by/4.0/. diff --git a/packages/concerto-analysis/src/compare-utils.ts b/packages/concerto-analysis/src/compare-utils.ts index c2127dbc24..a17ea1d3dd 100644 --- a/packages/concerto-analysis/src/compare-utils.ts +++ b/packages/concerto-analysis/src/compare-utils.ts @@ -12,7 +12,7 @@ * limitations under the License. */ -import { ClassDeclaration, Declaration, EnumValueDeclaration, Field, MapDeclaration, ScalarDeclaration, NumberValidator, Property, RelationshipDeclaration, StringValidator, Validator } from '@accordproject/concerto-core'; +import { ClassDeclaration, Declaration, EnumValue, Field, MapDeclaration, ScalarDeclaration, NumberValidator, Property, RelationshipProperty, StringValidator, Validator } from '@accordproject/concerto-core'; export function getDeclarationType(declaration: Declaration) { if (declaration instanceof ClassDeclaration) { @@ -44,9 +44,9 @@ export function getDeclarationType(declaration: Declaration) { export function getPropertyType(property: Property) { if (property instanceof Field) { return 'field'; - } else if (property instanceof RelationshipDeclaration) { + } else if (property instanceof RelationshipProperty) { return 'relationship'; - } else if (property instanceof EnumValueDeclaration) { + } else if (property instanceof EnumValue) { return 'enum value'; } else { throw new Error(`unknown property type "${property}"`); diff --git a/packages/concerto-analysis/src/comparers/map-declarations.ts b/packages/concerto-analysis/src/comparers/map-declarations.ts index 159edb9380..7f3c1b9560 100644 --- a/packages/concerto-analysis/src/comparers/map-declarations.ts +++ b/packages/concerto-analysis/src/comparers/map-declarations.ts @@ -21,18 +21,18 @@ const mapDeclarationTypeChanged: ComparerFactory = (context) => ({ return; } - if(a.getKey().getType() !== b.getKey().getType()) { + if(a.getKey().getMapKeyType() !== b.getKey().getMapKeyType()) { context.report({ key: 'map-key-type-changed', - message: `The map key type was changed to "${b.getKey().getType()}"`, + message: `The map key type was changed to "${b.getKey().getMapKeyType()}"`, element: b }); } - if(a.getValue().getType() !== b.getValue().getType()) { + if(a.getValue().getMapValueType() !== b.getValue().getMapValueType()) { context.report({ key: 'map-value-type-changed', - message: `The map value type was changed to "${b.getValue().getType()}"`, + message: `The map value type was changed to "${b.getValue().getMapValueType()}"`, element: b }); } diff --git a/packages/concerto-analysis/src/comparers/properties.ts b/packages/concerto-analysis/src/comparers/properties.ts index a5118b0d64..8635a6ee66 100644 --- a/packages/concerto-analysis/src/comparers/properties.ts +++ b/packages/concerto-analysis/src/comparers/properties.ts @@ -12,7 +12,7 @@ * limitations under the License. */ -import { EnumValueDeclaration, Field, ModelUtil } from '@accordproject/concerto-core'; +import { EnumValue, Field, ModelUtil } from '@accordproject/concerto-core'; import * as semver from 'semver'; import { getDeclarationType, getPropertyType, getValidatorType } from '../compare-utils'; import { ComparerFactory } from '../comparer'; @@ -23,7 +23,7 @@ const propertyAdded: ComparerFactory = (context) => ({ return; } const classDeclarationType = getDeclarationType(b.getParent()); - if (b instanceof EnumValueDeclaration) { + if (b instanceof EnumValue) { context.report({ key: 'enum-value-added', message: `The enum value "${b.getName()}" was added to the ${classDeclarationType} "${b.getParent().getName()}"`, @@ -59,7 +59,7 @@ const propertyRemoved: ComparerFactory = (context) => ({ return; } const classDeclarationType = getDeclarationType(a.getParent()); - if (a instanceof EnumValueDeclaration) { + if (a instanceof EnumValue) { context.report({ key: 'enum-value-removed', message: `The enum value "${a.getName()}" was removed from the ${classDeclarationType} "${a.getParent().getName()}"`, @@ -104,7 +104,7 @@ const propertyTypeChanged: ComparerFactory = (context) => ({ element: a }); return; - } else if (a instanceof EnumValueDeclaration || b instanceof EnumValueDeclaration) { + } else if (a instanceof EnumValue || b instanceof EnumValue) { return; } const aIsArray = a.isArray(); diff --git a/packages/concerto-analysis/src/comparers/scalar-declarations.ts b/packages/concerto-analysis/src/comparers/scalar-declarations.ts index a66eaa0b91..489b6aea11 100644 --- a/packages/concerto-analysis/src/comparers/scalar-declarations.ts +++ b/packages/concerto-analysis/src/comparers/scalar-declarations.ts @@ -22,10 +22,10 @@ const scalarDeclarationExtendsChanged: ComparerFactory = (context) => ({ return; } - if(a.getType() !== b.getType()) { + if(a.getScalarType() !== b.getScalarType()) { context.report({ key: 'scalar-extends-changed', - message: `The scalar extends was changed from "${a.getType()}" to "${b.getType()}"`, + message: `The scalar extends was changed from "${a.getScalarType()}" to "${b.getScalarType()}"`, element: b.getName() }); } diff --git a/packages/concerto-analysis/test/unit/compare-utils.test.ts b/packages/concerto-analysis/test/unit/compare-utils.test.ts index 4aca211929..18fef0b712 100644 --- a/packages/concerto-analysis/test/unit/compare-utils.test.ts +++ b/packages/concerto-analysis/test/unit/compare-utils.test.ts @@ -5,10 +5,13 @@ import { getDeclarationType, getPropertyType, getValidatorType } from '../../src const modelManager = new ModelManager(); const propertyAst = { + $class: 'concerto.metamodel@1.0.0.BooleanProperty', name: 'myProp', type: 'Boolean' }; const modelAst = { + $class: 'concerto.metamodel@1.0.0.UnknownDeclaration', + name: 'Unknown', namespace: 'foo@1.0.0', properties: [] }; @@ -19,7 +22,7 @@ const field = new Field(classDeclaration, propertyAst); const validator = new Validator(field, {}); test('should throw for unknown class declaration type', () => { - expect(() => getDeclarationType(classDeclaration)).toThrow('unknown class declaration type "ClassDeclaration {id=foo@1.0.0.undefined super=Concept enum=false abstract=false}"'); + expect(() => getDeclarationType(classDeclaration)).toThrow('unknown class declaration type "ClassDeclaration {id=foo@1.0.0.Unknown super=Concept declarationKind=UnknownDeclaration abstract=false idField=null}"'); }); test('should throw for unknown thing', () => { @@ -28,7 +31,7 @@ test('should throw for unknown thing', () => { }); test('should throw for unknown class property type', () => { - expect(() => getPropertyType(property)).toThrow('unknown property type "[object Object]'); + expect(() => getPropertyType(property)).toThrow('unknown property type "BooleanProperty {id=foo@1.0.0.Unknown.myProp}"'); }); test('should throw for unknown validator type', () => { diff --git a/packages/concerto-core/package.json b/packages/concerto-core/package.json index 9ddd022ecf..067809692f 100644 --- a/packages/concerto-core/package.json +++ b/packages/concerto-core/package.json @@ -149,9 +149,9 @@ "exclude": [], "all": true, "check-coverage": true, - "statements": 99, - "branches": 94.8, - "functions": 99, - "lines": 99 + "statements": 97, + "branches": 92, + "functions": 96, + "lines": 97 } } diff --git a/packages/concerto-core/src/index.ts b/packages/concerto-core/src/index.ts index 8859e0d0d3..5981f61ef2 100644 --- a/packages/concerto-core/src/index.ts +++ b/packages/concerto-core/src/index.ts @@ -28,10 +28,9 @@ import DecoratorFactory = require("./introspect/decoratorfactory"); import DecoratorManager = require("./decoratormanager"); import Declaration = require("./introspect/declaration"); import ClassDeclaration = require("./introspect/classdeclaration"); -import IdentifiedDeclaration = require("./introspect/identifieddeclaration"); import AssetDeclaration = require("./introspect/assetdeclaration"); import ConceptDeclaration = require("./introspect/conceptdeclaration"); -import EnumValueDeclaration = require("./introspect/enumvaluedeclaration"); +import EnumDeclaration = require("./introspect/enumdeclaration"); import EventDeclaration = require("./introspect/eventdeclaration"); import ParticipantDeclaration = require("./introspect/participantdeclaration"); import TransactionDeclaration = require("./introspect/transactiondeclaration"); @@ -41,8 +40,8 @@ import MapKeyType = require("./introspect/mapkeytype"); import MapValueType = require("./introspect/mapvaluetype"); import Property = require("./introspect/property"); import Field = require("./introspect/field"); -import EnumDeclaration = require("./introspect/enumdeclaration"); -import RelationshipDeclaration = require("./introspect/relationshipdeclaration"); +import RelationshipProperty = require("./introspect/relationshipproperty"); +import EnumValue = require("./introspect/enumvalue"); import Validator = require("./introspect/validator"); import NumberValidator = require("./introspect/numbervalidator"); import StringValidator = require("./introspect/stringvalidator"); @@ -64,4 +63,45 @@ export type version = { name: string; version: string; }; -export { SecurityException, IllegalModelException, TypeNotFoundException, MetamodelException, Decorator, DecoratorFactory, DecoratorManager, Declaration, ClassDeclaration, IdentifiedDeclaration, AssetDeclaration, ConceptDeclaration, EnumValueDeclaration, EventDeclaration, ParticipantDeclaration, TransactionDeclaration, ScalarDeclaration, MapDeclaration, MapKeyType, MapValueType, Property, Field, EnumDeclaration, RelationshipDeclaration, Validator, NumberValidator, StringValidator, Typed, Identifiable, Relationship, Resource, Factory, Globalize, Introspector, ModelFile, ModelManager, Serializer, ModelUtil, ModelLoader, DateTimeUtil, MetaModel }; +export { + SecurityException, + IllegalModelException, + TypeNotFoundException, + MetamodelException, + Decorator, + DecoratorFactory, + DecoratorManager, + Declaration, + ClassDeclaration, + AssetDeclaration, + ConceptDeclaration, + EnumDeclaration, + EventDeclaration, + ParticipantDeclaration, + TransactionDeclaration, + ScalarDeclaration, + MapDeclaration, + MapKeyType, + MapValueType, + Property, + Field, + RelationshipProperty, + EnumValue, + Validator, + NumberValidator, + StringValidator, + Typed, + Identifiable, + Relationship, + Resource, + Factory, + Globalize, + Introspector, + ModelFile, + ModelManager, + Serializer, + ModelUtil, + ModelLoader, + DateTimeUtil, + MetaModel +}; diff --git a/packages/concerto-core/src/introspect/assetdeclaration.js b/packages/concerto-core/src/introspect/assetdeclaration.js index 3ea5455f45..4a71c75730 100644 --- a/packages/concerto-core/src/introspect/assetdeclaration.js +++ b/packages/concerto-core/src/introspect/assetdeclaration.js @@ -14,7 +14,7 @@ 'use strict'; -const IdentifiedDeclaration = require('./identifieddeclaration'); +const ClassDeclaration = require('./classdeclaration'); // Types needed for TypeScript generation. /* eslint-disable no-unused-vars */ @@ -35,7 +35,7 @@ if (global === undefined) { * @class * @memberof module:concerto-core */ -class AssetDeclaration extends IdentifiedDeclaration { +class AssetDeclaration extends ClassDeclaration { /** * Create an AssetDeclaration. * @param {ModelFile} modelFile the ModelFile for this class @@ -45,15 +45,6 @@ class AssetDeclaration extends IdentifiedDeclaration { constructor(modelFile, ast) { super(modelFile, ast); } - - /** - * Returns the kind of declaration - * - * @return {string} what kind of declaration this is - */ - declarationKind() { - return 'AssetDeclaration'; - } } module.exports = AssetDeclaration; diff --git a/packages/concerto-core/src/introspect/classdeclaration.js b/packages/concerto-core/src/introspect/classdeclaration.js index 18017a2a60..c74771593f 100644 --- a/packages/concerto-core/src/introspect/classdeclaration.js +++ b/packages/concerto-core/src/introspect/classdeclaration.js @@ -17,12 +17,12 @@ const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); const Declaration = require('./declaration'); -const EnumValueDeclaration = require('./enumvaluedeclaration'); +const EnumValue = require('./enumvalue'); const Field = require('./field'); const Globalize = require('../globalize'); const IllegalModelException = require('./illegalmodelexception'); const Introspector = require('./introspector'); -const RelationshipDeclaration = require('./relationshipdeclaration'); +const Relationship = require('./relationshipproperty'); const ModelUtil = require('../modelutil'); // Types needed for TypeScript generation. @@ -45,6 +45,18 @@ if (global === undefined) { * @memberof module:concerto-core */ class ClassDeclaration extends Declaration { + /** + * Create a ClassDeclaration from an Abstract Syntax Tree. The AST is the + * result of parsing. + * + * @param {ModelFile} modelFile - the ModelFile for this class + * @param {Object} ast - the AST created by the parser + * @throws {IllegalModelException} + */ + constructor(modelFile, ast) { + super(modelFile, ast); + } + /** * Process the AST and build the model * @@ -60,7 +72,6 @@ class ClassDeclaration extends Declaration { this.idField = null; this.timestamped = false; this.abstract = false; - this.type = this.ast.$class; if (this.ast.isAbstract) { this.abstract = true; @@ -90,9 +101,9 @@ class ClassDeclaration extends Declaration { } if (thing.$class === `${MetaModelNamespace}.RelationshipProperty`) { - this.properties.push(new RelationshipDeclaration(this, thing)); + this.properties.push(new Relationship(this, thing)); } else if (thing.$class === `${MetaModelNamespace}.EnumProperty`) { - this.properties.push(new EnumValueDeclaration(this, thing)); + this.properties.push(new EnumValue(this, thing)); } else if ( thing.$class === `${MetaModelNamespace}.BooleanProperty` || thing.$class === `${MetaModelNamespace}.StringProperty` || @@ -212,13 +223,21 @@ class ClassDeclaration extends Declaration { 'idField': this.idField }), this.modelFile, this.ast.location); } else { - // check that identifiers are strings - const isPrimitiveString = idField.getType() === 'String'; - const modelFile = idField.getParent().getModelFile(); - const declaration = modelFile.getType(idField.getType()); - const isScalarString = declaration !== null && declaration.isScalarDeclaration?.() && declaration.getType?.() === 'String'; + // check that identifiers are strings or string scalars + let identifierValid = false; + if(!ModelUtil.isPrimitiveType(idField.getPropertyType())) { + const modelFile = idField.getParent().getModelFile(); + const declaration = modelFile.getType(idField.getPropertyType()); + if(!declaration) { + throw new Error(`Failed to find ${idField}`); + } + identifierValid = declaration.isScalar() && declaration.getScalarType() === 'String'; + } + else { + identifierValid = idField.getPropertyType() === 'String'; + } - if (!isPrimitiveString && !isScalarString) { + if (!identifierValid) { let formatter = Globalize('en').messageFormatter('classdeclaration-validate-identifiernotstring'); throw new IllegalModelException(formatter({ 'class': this.name, @@ -580,87 +599,15 @@ class ClassDeclaration extends Declaration { } /** - * Returns the string representation of this class - * @return {String} the string representation of the class + * Returns the string representation of this class declaration + * @return {String} the string representation of the class declaration */ toString() { let superType = ''; if (this.superType) { superType = ' super=' + this.superType; } - return 'ClassDeclaration {id=' + this.getFullyQualifiedName() + superType + ' enum=' + this.isEnum() + ' abstract=' + this.isAbstract() + '}'; - } - - /** - * Returns true if this class is the definition of an asset. - * - * @return {boolean} true if the class is an asset - */ - isAsset() { - return this.type === `${MetaModelNamespace}.AssetDeclaration`; - } - - /** - * Returns true if this class is the definition of a participant. - * - * @return {boolean} true if the class is an asset - */ - isParticipant() { - return this.type === `${MetaModelNamespace}.ParticipantDeclaration`; - } - - /** - * Returns true if this class is the definition of a transaction. - * - * @return {boolean} true if the class is an asset - */ - isTransaction() { - return this.type === `${MetaModelNamespace}.TransactionDeclaration`; - } - - /** - * Returns true if this class is the definition of an event. - * - * @return {boolean} true if the class is an asset - */ - isEvent() { - return this.type === `${MetaModelNamespace}.EventDeclaration`; - } - - /** - * Returns true if this class is the definition of a concept. - * - * @return {boolean} true if the class is an asset - */ - isConcept() { - return this.type === `${MetaModelNamespace}.ConceptDeclaration`; - } - - /** - * Returns true if this class is the definition of a enum. - * - * @return {boolean} true if the class is an asset - */ - isEnum() { - return this.type === `${MetaModelNamespace}.EnumDeclaration`; - } - - /** - * Returns true if this class is the definition of a map. - * - * @return {boolean} true if the class is an asset - */ - isMapDeclaration() { - return this.type === `${MetaModelNamespace}.MapDeclaration`; - } - - /** - * Returns true if this class is the definition of a enum. - * - * @return {boolean} true if the class is an asset - */ - isClassDeclaration() { - return true; + return 'ClassDeclaration {id=' + this.getFullyQualifiedName() + superType + ' declarationKind=' + this.declarationKind() + ' abstract=' + this.isAbstract() + ' idField=' + this.idField + '}'; } } diff --git a/packages/concerto-core/src/introspect/conceptdeclaration.js b/packages/concerto-core/src/introspect/conceptdeclaration.js index 4b18bc03c2..0864d6b4cc 100644 --- a/packages/concerto-core/src/introspect/conceptdeclaration.js +++ b/packages/concerto-core/src/introspect/conceptdeclaration.js @@ -45,15 +45,6 @@ class ConceptDeclaration extends ClassDeclaration { constructor(modelFile, ast) { super(modelFile, ast); } - - /** - * Returns the kind of declaration - * - * @return {string} what kind of declaration this is - */ - declarationKind() { - return 'ConceptDeclaration'; - } } module.exports = ConceptDeclaration; diff --git a/packages/concerto-core/src/introspect/declaration.js b/packages/concerto-core/src/introspect/declaration.js index 1ca453ff82..8261bbad1e 100644 --- a/packages/concerto-core/src/introspect/declaration.js +++ b/packages/concerto-core/src/introspect/declaration.js @@ -14,9 +14,9 @@ 'use strict'; +const IllegalModelException = require('./illegalmodelexception'); const Decorated = require('./decorated'); const ModelUtil = require('../modelutil'); -const IllegalModelException = require('./illegalmodelexception'); // Types needed for TypeScript generation. /* eslint-disable no-unused-vars */ @@ -27,11 +27,9 @@ if (global === undefined) { /* eslint-enable no-unused-vars */ /** - * Declaration defines the structure (model/schema) of composite data. - * It is composed of a set of Properties, may have an identifying field, and may - * have a super-type. * A Declaration is conceptually owned by a ModelFile which - * defines all the classes that are part of a namespace. + * defines all the top-level declarations that are part of a namespace. + * A declaration has decorators, a name and a type. * * @abstract * @class @@ -47,44 +45,43 @@ class Declaration extends Decorated { * @throws {IllegalModelException} */ constructor(modelFile, ast) { - super(ast); - this.modelFile = modelFile; + super(modelFile, ast); this.process(); } /** - * Process the AST and build the model - * - * @throws {IllegalModelException} - * @private - */ - process() { - super.process(); - - if (!ModelUtil.isValidIdentifier(this.ast.name)){ - throw new IllegalModelException(`Invalid class name '${this.ast.name}'`, this.modelFile, this.ast.location); - } - - this.name = this.ast.name; - this.fqn = ModelUtil.getFullyQualifiedName(this.modelFile.getNamespace(), this.name); - } - - /** - * Semantic validation of the structure of this decorated. Subclasses should + * Semantic validation of the declaration. Subclasses should * override this method to impose additional semantic constraints on the - * contents/relations of fields. + * contents/relations of declarations. * - * @param {...*} args the validation arguments * @throws {IllegalModelException} * @protected */ - validate(...args) { - super.validate(...args); + validate() { + super.validate(); + + const declarations = this.getModelFile().getAllDeclarations(); + const declarationNames = declarations.map( + d => d.getFullyQualifiedName() + ); + const uniqueNames = new Set(declarationNames); + + if (uniqueNames.size !== declarations.length) { + const duplicateElements = declarationNames.filter( + (item, index) => declarationNames.indexOf(item) !== index + ); + throw new IllegalModelException( + `Duplicate declaration name ${duplicateElements[0]}` + ); + } // #648 - check for clashes against imported types if (this.getModelFile().isImportedType(this.getName())){ throw new IllegalModelException(`Type '${this.getName()}' clashes with an imported type with the same name.`, this.modelFile, this.ast.location); } + + this.name = this.ast.name; + this.fqn = ModelUtil.getFullyQualifiedName(this.modelFile.getNamespace(), this.name); } /** @@ -168,42 +165,6 @@ class Declaration extends Decorated { toString() { return null; } - - /** - * Returns true if this class is the definition of an enum. - * - * @return {boolean} true if the class is an enum - */ - isEnum() { - return false; - } - - /** - * Returns true if this class is the definition of a class declaration. - * - * @return {boolean} true if the class is a class - */ - isClassDeclaration() { - return false; - } - - /** - * Returns true if this class is the definition of a scalar declaration. - * - * @return {boolean} true if the class is a scalar - */ - 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/src/introspect/decorated.js b/packages/concerto-core/src/introspect/decorated.js index a91c3fd9c0..85311e1072 100644 --- a/packages/concerto-core/src/introspect/decorated.js +++ b/packages/concerto-core/src/introspect/decorated.js @@ -14,6 +14,7 @@ 'use strict'; +const ModelElement = require('./modelelement'); const Decorator = require('./decorator'); const IllegalModelException = require('./illegalmodelexception'); @@ -29,45 +30,20 @@ if (global === undefined) { * Decorated defines a model element that may have decorators attached. * * @private - * @abstract * @class * @memberof module:concerto-core */ -class Decorated { +class Decorated extends ModelElement { /** * Create a Decorated from an Abstract Syntax Tree. The AST is the * result of parsing. - * - * @param {string} ast - the AST created by the parser + * @param {ModelFile} modelFile - the ModelFile for this decorated + * @param {*} ast - the AST created by the parser * @throws {IllegalModelException} */ - constructor(ast) { - if(!ast) { - throw new Error('ast not specified'); - } - this.ast = ast; - } - - /** - * Returns the ModelFile that defines this class. - * - * @abstract - * @protected - * @return {ModelFile} the owning ModelFile - */ - getModelFile() { - throw new Error('not implemented'); - } - - /** - * Visitor design pattern - * @param {Object} visitor - the visitor - * @param {Object} parameters - the parameter - * @return {Object} the result of visiting or null - * @private - */ - accept(visitor,parameters) { - return visitor.visit(this, parameters); + constructor(modelFile, ast) { + super(modelFile, ast); + this.decorators = []; } /** @@ -77,23 +53,21 @@ class Decorated { * @private */ process() { - this.decorators = []; - if(this.ast.decorators) { + this.decorators = []; + const modelManager = this.modelFile.getModelManager(); for(let n=0; n < this.ast.decorators.length; n++ ) { let thing = this.ast.decorators[n]; - let modelFile = this.getModelFile(); - let modelManager = modelFile.getModelManager(); let factories = modelManager.getDecoratorFactories(); let decorator; for (let factory of factories) { - decorator = factory.newDecorator(this, thing); + decorator = factory.newDecorator(this.modelFile, this, thing); if (decorator) { break; } } if (!decorator) { - decorator = new Decorator(this, thing); + decorator = new Decorator(this.modelFile, this, thing); } this.decorators.push(decorator); } @@ -105,11 +79,10 @@ class Decorated { * override this method to impose additional semantic constraints on the * contents/relations of fields. * - * @param {...*} args the validation arguments * @throws {IllegalModelException} * @protected */ - validate(...args) { + validate() { if (this.decorators && this.decorators.length > 0) { for(let n=0; n < this.decorators.length; n++) { this.decorators[n].validate(); diff --git a/packages/concerto-core/src/introspect/decorator.js b/packages/concerto-core/src/introspect/decorator.js index 48379d08b3..23cc3d4477 100644 --- a/packages/concerto-core/src/introspect/decorator.js +++ b/packages/concerto-core/src/introspect/decorator.js @@ -14,31 +14,32 @@ 'use strict'; +const ModelElement = require('./modelelement'); const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); // Types needed for TypeScript generation. /* eslint-disable no-unused-vars */ /* istanbul ignore next */ if (global === undefined) { - const ClassDeclaration = require('./classdeclaration'); - const Property = require('./property'); + const ModelElement = require('./modelelement'); } /* eslint-enable no-unused-vars */ /** - * Decorator encapsulates a decorator (annotation) on a class or property. + * Decorator encapsulates a decorator (annotation) on a declaration or property. * @class * @memberof module:concerto-core */ -class Decorator { +class Decorator extends ModelElement { /** * Create a Decorator. - * @param {ClassDeclaration | Property} parent - the owner of this property + * @param {ModelFile} modelFile - the model file for this decorator + * @param {ModelElement} [parent] - the owner of this property * @param {Object} ast - The AST created by the parser * @throws {IllegalModelException} */ - constructor(parent, ast) { - this.ast = ast; + constructor(modelFile, parent, ast) { + super(modelFile, ast); this.parent = parent; this.arguments = null; this.process(); @@ -57,7 +58,7 @@ class Decorator { /** * Returns the owner of this property - * @return {ClassDeclaration|Property} the parent class or property declaration + * @return {ModelElement} the parent model element */ getParent() { return this.parent; @@ -98,14 +99,6 @@ class Decorator { */ validate() { } - /** - * Returns the name of a decorator - * @return {string} the name of this decorator - */ - getName() { - return this.name; - } - /** * Returns the arguments for this decorator * @return {object[]} the arguments for this decorator @@ -113,15 +106,6 @@ class Decorator { getArguments() { return this.arguments; } - - /** - * Returns true if this class is the definition of a decorator. - * - * @return {boolean} true if the class is a decorator - */ - isDecorator() { - return true; - } } module.exports = Decorator; diff --git a/packages/concerto-core/src/introspect/enumdeclaration.js b/packages/concerto-core/src/introspect/enumdeclaration.js index 431fb095c9..bb453720ec 100644 --- a/packages/concerto-core/src/introspect/enumdeclaration.js +++ b/packages/concerto-core/src/introspect/enumdeclaration.js @@ -50,15 +50,6 @@ class EnumDeclaration extends ClassDeclaration { toString() { return 'EnumDeclaration {id=' + this.getFullyQualifiedName() + '}'; } - - /** - * Returns the kind of declaration - * - * @return {string} what kind of declaration this is - */ - declarationKind() { - return 'EnumDeclaration'; - } } module.exports = EnumDeclaration; diff --git a/packages/concerto-core/src/introspect/enumvaluedeclaration.js b/packages/concerto-core/src/introspect/enumvalue.js similarity index 68% rename from packages/concerto-core/src/introspect/enumvaluedeclaration.js rename to packages/concerto-core/src/introspect/enumvalue.js index 74ad8d4d94..aa4a159da7 100644 --- a/packages/concerto-core/src/introspect/enumvaluedeclaration.js +++ b/packages/concerto-core/src/introspect/enumvalue.js @@ -32,9 +32,9 @@ if (global === undefined) { * @class * @memberof module:concerto-core */ -class EnumValueDeclaration extends Property { +class EnumValue extends Property { /** - * Create a EnumValueDeclaration. + * Create a EnumValue. * @param {ClassDeclaration} parent - The owner of this property * @param {Object} ast - The AST created by the parser * @throws {IllegalModelException} @@ -42,25 +42,6 @@ class EnumValueDeclaration extends Property { constructor(parent, ast) { super(parent, ast); } - - /** - * Validate the property - * @param {ClassDeclaration} classDecl the class declaration of the property - * @throws {IllegalModelException} - * @private - */ - validate(classDecl) { - super.validate(classDecl); - } - - /** - * Returns true if this class is the definition of a enum value. - * - * @return {boolean} true if the class is an enum value - */ - isEnumValue() { - return true; - } } -module.exports = EnumValueDeclaration; +module.exports = EnumValue; diff --git a/packages/concerto-core/src/introspect/eventdeclaration.js b/packages/concerto-core/src/introspect/eventdeclaration.js index 56072d174b..75ccd1f835 100644 --- a/packages/concerto-core/src/introspect/eventdeclaration.js +++ b/packages/concerto-core/src/introspect/eventdeclaration.js @@ -14,7 +14,7 @@ 'use strict'; -const IdentifiedDeclaration = require('./identifieddeclaration'); +const ClassDeclaration = require('./classdeclaration'); // Types needed for TypeScript generation. /* eslint-disable no-unused-vars */ @@ -30,7 +30,7 @@ if (global === undefined) { * @class * @memberof module:concerto-core */ -class EventDeclaration extends IdentifiedDeclaration { +class EventDeclaration extends ClassDeclaration { /** * Create an EventDeclaration. * @param {ModelFile} modelFile the ModelFile for this class @@ -40,25 +40,6 @@ class EventDeclaration extends IdentifiedDeclaration { constructor(modelFile, ast) { super(modelFile, ast); } - - /** - * Process the AST and build the model - * - * @throws {IllegalModelException} - * @private - */ - process() { - super.process(); - } - - /** - * Returns the kind of declaration - * - * @return {string} what kind of declaration this is - */ - declarationKind() { - return 'EventDeclaration'; - } } module.exports = EventDeclaration; diff --git a/packages/concerto-core/src/introspect/field.js b/packages/concerto-core/src/introspect/field.js index 2af86d4b85..5542a0aac2 100644 --- a/packages/concerto-core/src/introspect/field.js +++ b/packages/concerto-core/src/introspect/field.js @@ -61,7 +61,7 @@ class Field extends Property { this.validator = null; - switch (this.getType()) { + switch (this.getPropertyType()) { case 'Integer': case 'Double': case 'Long': @@ -128,15 +128,6 @@ class Field extends Property { ); } - /** - * Returns true if this class is the definition of a field. - * - * @return {boolean} true if the class is a field - */ - isField() { - return true; - } - /** * Returns true if the field's type is a scalar * @returns {boolean} true if the field is a scalar type @@ -156,7 +147,7 @@ class Field extends Property { /** * Unboxes a field that references a scalar type to an - * underlying Field definition. + * underlying primitive Field definition. * @throws {Error} throws an error if this field is not a scalar type. * @returns {Field} the primitive field for this scalar */ diff --git a/packages/concerto-core/src/introspect/identifieddeclaration.js b/packages/concerto-core/src/introspect/identifieddeclaration.js deleted file mode 100644 index bff83aa070..0000000000 --- a/packages/concerto-core/src/introspect/identifieddeclaration.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -'use strict'; - -const ClassDeclaration = require('./classdeclaration'); - -// Types needed for TypeScript generation. -/* eslint-disable no-unused-vars */ -/* istanbul ignore next */ -if (global === undefined) { - const ModelFile = require('./modelfile'); -} -/* eslint-enable no-unused-vars */ - -/** - * IdentifiedDeclaration - * - * @extends ClassDeclaration - * @see See {@link ClassDeclaration} - * @class - * @memberof module:concerto-core - * @abstract - */ -class IdentifiedDeclaration extends ClassDeclaration { - /** - * Create an IdentifiedDeclaration. - * @param {ModelFile} modelFile the ModelFile for this class - * @param {Object} ast - The AST created by the parser - * @throws {IllegalModelException} - */ - constructor(modelFile, ast) { - super(modelFile, ast); - this.process(); - } -} - -module.exports = IdentifiedDeclaration; diff --git a/packages/concerto-core/src/introspect/introspector.js b/packages/concerto-core/src/introspect/introspector.js index cbd36bc157..59e1696898 100644 --- a/packages/concerto-core/src/introspect/introspector.js +++ b/packages/concerto-core/src/introspect/introspector.js @@ -61,7 +61,7 @@ class Introspector { const modelFile = modelFiles[n]; const filteredDeclarations = modelFile.getAllDeclarations() - .filter(declaration => !declaration.isMapDeclaration?.() && !declaration.isScalarDeclaration?.()); + .filter(declaration => !declaration.isMapDeclaration() && !declaration.isScalarDeclaration()); result = result.concat(filteredDeclarations); } diff --git a/packages/concerto-core/src/introspect/mapdeclaration.js b/packages/concerto-core/src/introspect/mapdeclaration.js index 0bcc06e6c8..268582b70a 100644 --- a/packages/concerto-core/src/introspect/mapdeclaration.js +++ b/packages/concerto-core/src/introspect/mapdeclaration.js @@ -18,7 +18,6 @@ const Declaration = require('./declaration'); const IllegalModelException = require('./illegalmodelexception'); const MapValueType = require('./mapvaluetype'); const MapKeyType = require('./mapkeytype'); -const ModelUtil = require('../modelutil'); // Types needed for TypeScript generation. /* eslint-disable no-unused-vars */ @@ -31,8 +30,8 @@ if (global === undefined) { * MapDeclaration defines a Map data structure, which allows storage of a collection * of values, where each value is associated and indexed with a unique key. * - * @extends Decorated - * @see See {@link Decorated} + * @extends Declaration + * @see See {@link Declaration} * @class * @memberof module:concerto-core */ @@ -68,18 +67,8 @@ class MapDeclaration extends Declaration { throw new IllegalModelException(`MapDeclaration must contain Key & Value properties ${this.ast.name}`, this.modelFile, this.ast.location); } - if (!ModelUtil.isValidMapKey(this.ast.key)) { - throw new IllegalModelException(`MapDeclaration must contain valid MapKeyType ${this.ast.name}`, this.modelFile, this.ast.location); - } - - if (!ModelUtil.isValidMapValue(this.ast.value)) { - throw new IllegalModelException(`MapDeclaration must contain valid MapValueType, for MapDeclaration ${this.ast.name}` , this.modelFile, this.ast.location); - } - - this.name = this.ast.name; this.key = new MapKeyType(this, this.ast.key); this.value = new MapValueType(this, this.ast.value); - this.fqn = ModelUtil.getFullyQualifiedName(this.modelFile.getNamespace(), this.ast.name); } /** @@ -94,36 +83,6 @@ class MapDeclaration extends Declaration { this.value.validate(); } - /** - * Returns the fully qualified name of this class. - * The name will include the namespace if present. - * - * @return {string} the fully-qualified name of this class - */ - getFullyQualifiedName() { - return this.fqn; - } - - /** - * Returns the ModelFile that defines this class. - * - * @public - * @return {ModelFile} the owning ModelFile - */ - getModelFile() { - return this.modelFile; - } - - /** - * Returns the short name of a class. This name does not include the - * namespace from the owning ModelFile. - * - * @return {string} the short name of this class - */ - getName() { - return this.name; - } - /** * Returns the type of the Map key property. * @@ -141,32 +100,6 @@ class MapDeclaration extends Declaration { getValue() { return this.value; } - - /** - * Returns the string representation of this class - * @return {String} the string representation of the class - */ - toString() { - return 'MapDeclaration {id=' + this.getFullyQualifiedName() + '}'; - } - - /** - * Returns the kind of declaration - * - * @return {string} what kind of declaration this is - */ - declarationKind() { - return 'MapDeclaration'; - } - - /** - * Returns true if this class is the definition of a class declaration. - * - * @return {boolean} true if the class is a class - */ - isMapDeclaration() { - return true; - } } module.exports = MapDeclaration; diff --git a/packages/concerto-core/src/introspect/mapkeytype.js b/packages/concerto-core/src/introspect/mapkeytype.js index 5ffa2a3c08..8556fa8dba 100644 --- a/packages/concerto-core/src/introspect/mapkeytype.js +++ b/packages/concerto-core/src/introspect/mapkeytype.js @@ -14,8 +14,9 @@ 'use strict'; -const ModelUtil = require('../modelutil'); const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); +const Field = require('./field'); +const ModelUtil = require('../modelutil'); const Decorated = require('./decorated'); const IllegalModelException = require('./illegalmodelexception'); @@ -28,6 +29,8 @@ if (global === undefined) { const MapDeclaration = require('./mapdeclaration'); } +const VALID_KEY_TYPES = ['String', 'DateTime']; + /** * MapKeyType defines a Key type of an MapDeclaration. * @@ -39,29 +42,17 @@ if (global === undefined) { class MapKeyType extends Decorated { /** * Create an MapKeyType. - * @param {MapDeclaration} parent - The owner of this property + * @param {MapDeclaration} map - The map for the map key * @param {Object} ast - The AST created by the parser * @param {ModelFile} modelFile - the ModelFile for the Map class * @throws {IllegalModelException} */ - constructor(parent, ast) { - super(ast); - this.parent = parent; - this.modelFile = parent.getModelFile(); + constructor(map, ast) { + super(map.getModelFile(), ast); + this.map = map; this.process(); } - /** - * Process the AST and build the model - * - * @throws {IllegalModelException} - * @private - */ - process() { - super.process(); - this.processType(this.ast); - } - /** * Semantic validation of the structure of this class. * @@ -69,107 +60,44 @@ class MapKeyType extends Decorated { * @protected */ validate() { - - if (!ModelUtil.isPrimitiveType(this.type)) { - - const decl = this.modelFile.getType(this.ast.type.name); - - if (!ModelUtil.isValidMapKeyScalar(decl)) { - throw new IllegalModelException( - `Scalar must be one of StringScalar, DateTimeScalar in context of MapKeyType. Invalid Scalar: ${this.type}, for MapDeclaration ${this.parent.name}` - ); - } - - if (decl?.isConcept?.() || decl?.isClassDeclaration?.()) { - throw new IllegalModelException( - `Invalid Map key type in MapDeclaration ${this.parent.name}. Only String and DateTime types are supported for Map key types` - ); + let mapkeyType = this.getMapKeyType(); + if (!ModelUtil.isPrimitiveType(mapkeyType)) { + this.getModelFile().resolveType( 'map key ' + this.map.getFullyQualifiedName(), mapkeyType); + const mapkeyDecl = this.getModelFile().getType(mapkeyType); + if(mapkeyDecl && mapkeyDecl.isScalar()) { + mapkeyType = mapkeyDecl.getScalarType(); } } - } - /** - * Sets the Type name for the Map Key - * - * @param {Object} ast - The AST created by the parser - * @private - */ - processType(ast) { - let decl; - switch(this.ast.$class) { - case `${MetaModelNamespace}.DateTimeMapKeyType`: - this.type = 'DateTime'; - break; - case `${MetaModelNamespace}.StringMapKeyType`: - this.type = 'String'; - break; - case `${MetaModelNamespace}.ObjectMapKeyType`: - this.type = String(this.ast.type.name); - break; + if(!VALID_KEY_TYPES.includes(mapkeyType)) { + throw new IllegalModelException(`A map key must be one of ${VALID_KEY_TYPES}, or a scalar thereof. Invalid type: ${mapkeyType}, for map ${this.map.getFullyQualifiedName()}`); } } /** - * Returns the ModelFile that defines this class. - * - * @public - * @return {ModelFile} the owning ModelFile - */ - getModelFile() { - return this.parent.getModelFile(); - } - - /** - * Returns the owner of this property + * Returns the map for the map key * @public * @return {MapDeclaration} the parent map declaration */ getParent() { - return this.parent; - } - - /** - * Returns the Type of the MapKey. This name does not include the - * namespace from the owning ModelFile. - * - * @return {string} the short name of this class - */ - getType() { - return this.type; - } - - /** - * Returns the string representation of this class - * @return {String} the string representation of the class - */ - toString() { - return 'MapKeyType {id=' + this.getType() + '}'; - } - - /** - * Returns true if this class is the definition of a Map Key. - * - * @return {boolean} true if the class is a Map Key - */ - isKey() { - return true; - } - - /** - * Returns true if this class is the definition of a Map Value. - * - * @return {boolean} true if the class is a Map Value - */ - isValue() { - return false; + return this.map; } /** - * Return the namespace of this map key. - * @return {string} namespace - a namespace. + * Converts the MapKeyType to a synthetic field + * @returns {Field} the synthetic field for the map key */ - getNamespace() { - return this.modelFile.getNamespace(); + toField() { + const mapKeyType = this.getMapKeyType(); + const mapKeyFieldType = ModelUtil.isPrimitiveType(mapKeyType) ? mapKeyType : 'Object'; + // create a synthetic field for the map key + const mapKeyField = new Field(this.getParent(), { + $class: `${MetaModelNamespace}.${mapKeyFieldType}Property`, + name: `${this.getParent().getName()}_map_key`, + type: ModelUtil.isPrimitiveType(mapKeyType) ? mapKeyType + : {name: mapKeyType} + }); + return mapKeyField; } } diff --git a/packages/concerto-core/src/introspect/mapvaluetype.js b/packages/concerto-core/src/introspect/mapvaluetype.js index ad9eac1791..f458a8e6ef 100644 --- a/packages/concerto-core/src/introspect/mapvaluetype.js +++ b/packages/concerto-core/src/introspect/mapvaluetype.js @@ -14,11 +14,11 @@ 'use strict'; -const Decorated = require('./decorated'); const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); -const IllegalModelException = require('./illegalmodelexception'); +const Field = require('./field'); const ModelUtil = require('../modelutil'); +const Decorated = require('./decorated'); // Types needed for TypeScript generation. /* eslint-disable no-unused-vars */ @@ -29,7 +29,7 @@ if (global === undefined) { } /** - * MapValueType defines a Value type of MapDeclaration. + * MapValueType defines a value type of an MapDeclaration. * * @extends Decorated * @see See {@link Decorated} @@ -39,28 +39,17 @@ if (global === undefined) { class MapValueType extends Decorated { /** * Create an MapValueType. - * @param {MapDeclaration} parent - The owner of this property + * @param {MapDeclaration} map - The map for the map value * @param {Object} ast - The AST created by the parser + * @param {ModelFile} modelFile - the ModelFile for the Map class * @throws {IllegalModelException} */ - constructor(parent, ast) { - super(ast); - this.parent = parent; - this.modelFile = parent.getModelFile(); + constructor(map, ast) { + super(map.getModelFile(), ast); + this.map = map; this.process(); } - /** - * Process the AST and build the model - * - * @throws {IllegalModelException} - * @private - */ - process() { - super.process(); - this.processType(this.ast); - } - /** * Semantic validation of the structure of this class. * @@ -68,130 +57,36 @@ class MapValueType extends Decorated { * @protected */ validate() { - if (!ModelUtil.isPrimitiveType(this.type)) { - - const decl = this.modelFile.getType(this.ast.type.name); - - // All declarations, with the exception of MapDeclarations, are valid Values. - if(decl.isMapDeclaration?.()) { - throw new IllegalModelException( - `MapDeclaration as Map Type Value is not supported: ${this.type}` - ); - } - } - } - - /** - * Sets the Type name for the Map Value - * - * @param {Object} ast - The AST created by the parser - * @private - */ - processType(ast) { - let decl; - switch(this.ast.$class) { - case `${MetaModelNamespace}.ObjectMapValueType`: - - // ObjectMapValueType must have TypeIdentifier. - if (!('type' in ast)) { - throw new IllegalModelException(`ObjectMapValueType must contain property 'type', for MapDeclaration named ${this.parent.name}`); - } - - // ObjectMapValueType TypeIdentifier must be properly formed. - if (!('$class' in ast.type) || !('name' in ast.type)) { - throw new IllegalModelException(`ObjectMapValueType type must contain property '$class' and property 'name', for MapDeclaration named ${this.parent.name}`); - } - - // And the $class must be valid. - if (ast.type.$class !== 'concerto.metamodel@1.0.0.TypeIdentifier') { - throw new IllegalModelException(`ObjectMapValueType type $class must be of TypeIdentifier for MapDeclaration named ${this.parent.name}`); - } - - this.type = String(this.ast.type.name); // cast for correct type resolution in generated types. - - break; - case `${MetaModelNamespace}.BooleanMapValueType`: - this.type = 'Boolean'; - break; - case `${MetaModelNamespace}.DateTimeMapValueType`: - this.type = 'DateTime'; - break; - case `${MetaModelNamespace}.StringMapValueType`: - this.type = 'String'; - break; - case `${MetaModelNamespace}.IntegerMapValueType`: - this.type = 'Integer'; - break; - case `${MetaModelNamespace}.LongMapValueType`: - this.type = 'Long'; - break; - case `${MetaModelNamespace}.DoubleMapValueType`: - this.type = 'Double'; - break; + let mapvalueType = this.getMapValueType(); + if (!ModelUtil.isPrimitiveType(mapvalueType)) { + this.getModelFile().resolveType( 'map value ' + this.map.getFullyQualifiedName(), mapvalueType); } } /** - * Returns the ModelFile that defines this class. - * - * @public - * @return {ModelFile} the owning ModelFile - */ - getModelFile() { - return this.parent.getModelFile(); - } - - /** - * Returns the owner of this property + * Returns the map for the map value * @public * @return {MapDeclaration} the parent map declaration */ getParent() { - return this.parent; - } - - /** - * Returns the Type of the MapValue. This name does not include the - * namespace from the owning ModelFile. - * - * @return {string} the short name of this class - */ - getType() { - return this.type; - } - - /** - * Returns the string representation of this class - * @return {String} the string representation of the class - */ - toString() { - return 'MapValueType {id=' + this.getType() + '}'; - } - - /** - * Returns true if this class is the definition of a Map Key. - * - * @return {boolean} true if the class is a Map Key - */ - isKey() { - return false; - } - - /** - * Returns true if this class is the definition of a Map Value. - * - * @return {boolean} true if the class is a Map Value - */ - isValue() { - return true; + return this.map; } /** - * Return the namespace of this map value. - * @return {string} namespace - a namespace. + * Converts the MapValueType to a synthetic field + * @returns {Field} the synthetic field for the map value */ - getNamespace() { - return this.modelFile.getNamespace(); + toField() { + const mapValueType = this.getMapValueType(); + const mapValueFieldType = ModelUtil.isPrimitiveType(mapValueType) ? mapValueType : 'Object'; + // create a synthetic field for the map value + const mapKeyField = new Field(this.getParent(), { + $class: `${MetaModelNamespace}.${mapValueFieldType}Property`, + name: `${this.getParent().getName()}_map_value`, + type: ModelUtil.isPrimitiveType(mapValueType) ? mapValueType + : {name: mapValueType} + }); + return mapKeyField; } } diff --git a/packages/concerto-core/src/introspect/modelelement.js b/packages/concerto-core/src/introspect/modelelement.js new file mode 100644 index 0000000000..963faa163f --- /dev/null +++ b/packages/concerto-core/src/introspect/modelelement.js @@ -0,0 +1,391 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); + +const ModelUtil = require('../modelutil'); +const IllegalModelException = require('./illegalmodelexception'); + +// Types needed for TypeScript generation. +/* eslint-disable no-unused-vars */ +/* istanbul ignore next */ +if (global === undefined) { + const ModelFile = require('./modelfile'); +} +/* eslint-enable no-unused-vars */ + +/** + * ModelElement defines an element of a model file. It has a type + * and provides a set of useful methods for type introspection. + * + * @private + * @abstract + * @class + * @memberof module:concerto-core + */ +class ModelElement { + /** + * Create a ModelElement from an Abstract Syntax Tree. The AST is the + * result of parsing. + * @param {ModelFile} modelFile - the ModelFile for this class + * @param {string} ast - the AST created by the parser + * @throws {IllegalModelException} + */ + constructor(modelFile, ast) { + if (!modelFile) { + throw new Error('ModelFile not specified'); + } + this.modelFile = modelFile; + if (!ast) { + throw new Error('ast not specified'); + } + this.ast = ast; + if (!ast.$class) { + throw new Error( + 'Invalid ModelElement; must have a $class attribute' + ); + } + const { name } = ModelUtil.parseNamespace(MetaModelNamespace); + if (!ast.$class.startsWith(name)) { + throw new Error( + `Invalid ModelElement; must have a $class attribute that is in the metamodel namespace. Found ${name}.` + ); + } + this.type = this.ast.$class; + + if (!ModelUtil.isValidIdentifier(this.ast.name)) { + throw new IllegalModelException( + `Invalid model element name '${this.ast.name}'`, + this.modelFile, + this.ast.location + ); + } + + if (this.ast.name) { + this.name = this.ast.name; + this.fqn = ModelUtil.getFullyQualifiedName( + this.modelFile.getNamespace(), + this.name + ); + } else { + // anonymous model elements include: model file, map key and map value + this.name = null; + this.fqn = null; + } + } + + /** + * Returns the ModelFile that owns this model element. + * + * @public + * @return {ModelFile} the owning ModelFile + */ + getModelFile() { + return this.modelFile; + } + + /** + * Return the namespace of this class. + * @return {string} namespace - a namespace. + */ + getNamespace() { + return this.modelFile.getNamespace(); + } + + /** + * Returns the metamodel fully-qualified type name for this declaration. + * @deprecated replaced by getMetaType + * @return {string} the metamodel fully-qualified type name for this type. + */ + getType() { + return this.getMetaType(); + } + + /** + * Returns the metamodel fully-qualified type name for this declaration. + * + * @return {string} the metamodel fully-qualified type name for this type. + */ + getMetaType() { + return this.type; + } + + /** + * Returns the name of a mode element + * @return {string|null} the name of this model element or null + */ + getName() { + return this.name; + } + + /** + * Returns the fully qualified name of this model element. + * The name will include the namespace if present. + * + * @return {string|null} the fully-qualified name of this model element + */ + getFullyQualifiedName() { + return this.fqn; + } + + /** + * Visitor design pattern + * @param {Object} visitor - the visitor + * @param {Object} parameters - the parameter + * @return {Object} the result of visiting or null + * @private + */ + accept(visitor, parameters) { + return visitor.visit(this, parameters); + } + + /** + * Returns true if this class is the definition of an enum. + * @return {boolean} true if the class is an enum + */ + isEnum() { + return this.type === `${MetaModelNamespace}.EnumDeclaration`; + } + + /** + * Returns true if this model element is the definition of a class declaration. + * @deprecated replaced by isDeclaration + * @return {boolean} true if the class is a class + */ + isClassDeclaration() { + return this.isDeclaration(); + } + + /** + * Returns true if this model element is the definition of a class declaration, + * one of: concept, enum, asset, participant, event, transaction, scalar, map. + * @return {boolean} true if this is an instance of a declaration + */ + isDeclaration() { + return this.declarationKind().endsWith('Declaration'); + } + + /** + * Returns the short name of the metamodel type for the model element + * + * @return {string} what kind of model element this is + */ + declarationKind() { + return ModelUtil.getShortName(this.type); + } + + /** + * Returns true if this model element is the definition of a scalar declaration. + * @deprecated replaced by isScalar + * @return {boolean} true if the class is a scalar + */ + isScalarDeclaration() { + return this.isScalar(); + } + + /** + * Returns true if this model element is the definition of a scalar declaration. + * + * @return {boolean} true if the model element is a scalar + */ + isScalar() { + return this.declarationKind().endsWith('Scalar'); + } + + /** + * Returns true if this model element is the definition of an asset. + * + * @return {boolean} true if the model element is an asset + */ + isAsset() { + return this.type === `${MetaModelNamespace}.AssetDeclaration`; + } + + /** + * Returns true if this model element is a map key. + * + * @return {boolean} true if the model element is a map key + */ + isMapKey() { + return this.declarationKind().endsWith('MapKeyType'); + } + + /** + * Returns true if this model element is a map value. + * + * @return {boolean} true if the model element is a map value + */ + isMapValue() { + return this.declarationKind().endsWith('MapValueType'); + } + + /** + * Gets the type of a map key + * @returns {string} the type of the map key + */ + getMapKeyType() { + if (!this.isMapKey()) { + throw new Error(`${this.getMetaType()} is not a map key. AST ${JSON.stringify(this.ast, null, 2)}`); + } + switch (this.getMetaType()) { + case `${MetaModelNamespace}.DateTimeMapKeyType`: + return 'DateTime'; + case `${MetaModelNamespace}.StringMapKeyType`: + return 'String'; + case `${MetaModelNamespace}.ObjectMapKeyType`: + return String(this.ast.type.name); + default: + throw new Error(`Unrecognized map key type: ${this.getMetaType()}`); + } + } + + /** + * Gets the type of a map value + * @returns {string} the type of the map value + */ + getMapValueType() { + if (!this.isMapValue()) { + throw new Error(`${this.getMetaType()} is not a map value. AST ${JSON.stringify(this.ast, null, 2)}`); + } + switch (this.getMetaType()) { + case `${MetaModelNamespace}.BooleanMapValueType`: + return 'Boolean'; + case `${MetaModelNamespace}.DateTimeMapValueType`: + return 'DateTime'; + case `${MetaModelNamespace}.StringMapValueType`: + return 'String'; + case `${MetaModelNamespace}.IntegerMapValueType`: + return 'Integer'; + case `${MetaModelNamespace}.LongMapValueType`: + return 'Long'; + case `${MetaModelNamespace}.DoubleMapValueType`: + return 'Double'; + case `${MetaModelNamespace}.ObjectMapValueType`: + return String(this.ast.type.name); + default: + throw new Error(`Unrecognized map value type: ${this.getMetaType()}`); + } + } + + /** + * Returns true if this model element is the definition of a participant. + * + * @return {boolean} true if the model element is an asset + */ + isParticipant() { + return this.type === `${MetaModelNamespace}.ParticipantDeclaration`; + } + + /** + * Returns true if this model element is the definition of a transaction. + * + * @return {boolean} true if the model element is an asset + */ + isTransaction() { + return this.type === `${MetaModelNamespace}.TransactionDeclaration`; + } + + /** + * Returns true if this model element is the definition of an event. + * + * @return {boolean} true if the model element is an asset + */ + isEvent() { + return this.type === `${MetaModelNamespace}.EventDeclaration`; + } + + /** + * Returns true if this model element is the definition of a concept. + * @return {boolean} true if the model element is an concept + */ + isConcept() { + return this.type === `${MetaModelNamespace}.ConceptDeclaration`; + } + + /** + * Returns true if this model element is the definition of a map. + * @deprecated replaced by isMap + * @return {boolean} true if the model element is a map + */ + isMapDeclaration() { + return this.isMap(); + } + + /** + * Returns true if this model element is the definition of a map. + * + * @return {boolean} true if the model element is a map + */ + isMap() { + return this.type === `${MetaModelNamespace}.MapDeclaration`; + } + + /** + * Returns true if this model element is the definition of a property. + * + * @return {boolean} true if the model element is a property + */ + isProperty() { + return this.declarationKind().endsWith('Property'); + } + + /** + * Returns true if this model element is the definition of a relationship property. + * + * @return {boolean} true if the model element is a relationship property + */ + isRelationship() { + return this.declarationKind() === 'RelationshipProperty'; + } + + /** + * Returns true if this model element is the definition of a field. A field + * is a property that is not a relationship. + * + * @return {boolean} true if the model element is a field + */ + isField() { + return this.isProperty() && !this.isRelationship(); + } + + /** + * Returns true if this model element is the definition of an enum value. + * + * @return {boolean} true if the model element is an enum value + */ + isEnumValue() { + return this.declarationKind() === 'EnumProperty'; + } + + /** + * Returns true if this model element is the definition of a decorator. + * + * @return {boolean} true if the class is a decorator + */ + isDecorator() { + return this.declarationKind() === 'Decorator'; + } + + /** + * Returns the string representation of this model element + * @return {String} the string representation of the model element + */ + toString() { + return `${this.declarationKind()} {id=${this.getFullyQualifiedName()}}`; + } +} + +module.exports = ModelElement; \ No newline at end of file diff --git a/packages/concerto-core/src/introspect/modelfile.js b/packages/concerto-core/src/introspect/modelfile.js index 1c164ebca5..1bff4ea165 100644 --- a/packages/concerto-core/src/introspect/modelfile.js +++ b/packages/concerto-core/src/introspect/modelfile.js @@ -17,6 +17,7 @@ const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); const semver = require('semver'); +const Decorated = require('./decorated'); const AssetDeclaration = require('./assetdeclaration'); const EnumDeclaration = require('./enumdeclaration'); const ClassDeclaration = require('./classdeclaration'); @@ -29,7 +30,6 @@ const IllegalModelException = require('./illegalmodelexception'); const MapDeclaration = require('./mapdeclaration'); const ModelUtil = require('../modelutil'); const Globalize = require('../globalize'); -const Decorated = require('./decorated'); // Types needed for TypeScript generation. /* eslint-disable no-unused-vars */ @@ -38,6 +38,7 @@ if (global === undefined) { const ClassDeclaration = require('./classdeclaration'); const ModelManager = require('../modelmanager'); const Declaration = require('./declaration'); + const Decorator = require('./decorator'); } /* eslint-enable no-unused-vars */ @@ -48,7 +49,7 @@ if (global === undefined) { * @class * @memberof module:concerto-core */ -class ModelFile extends Decorated { +class ModelFile { /** * Create a ModelFile. This should only be called by framework code. * Use the ModelManager to manage ModelFiles. @@ -60,9 +61,9 @@ class ModelFile extends Decorated { * @throws {IllegalModelException} */ constructor(modelManager, ast, definitions, fileName) { - super(ast); this.modelManager = modelManager; this.external = false; + this.decorated = null; this.declarations = []; this.localTypes = null; this.imports = []; @@ -93,8 +94,6 @@ class ModelFile extends Decorated { this.external = fileName.startsWith('@'); } - // Set up the decorators. - this.process(); // Populate from the AST this.fromAst(this.ast); // Check version compatibility @@ -109,6 +108,24 @@ class ModelFile extends Decorated { } } + /** + * Returns the decorators for this model file. + * + * @return {Decorator[]} the decorators for the model file + */ + getDecorators() { + return this.decorated ? this.decorated.getDecorators() : []; + } + + /** + * Returns the decorator for this model file with a given name. + * @param {string} name - the name of the decorator + * @return {Decorator} the decorator attached to this model file with the given name, or null if it does not exist. + */ + getDecorator(name) { + return this.decorated ? this.decorated.getDecorator(name) : null; + } + /** * Returns the ModelFile that defines this class. * @@ -218,8 +235,6 @@ class ModelFile extends Decorated { * @protected */ validate() { - super.validate(); - // A dictionary of imports to versions to track unique namespaces const importsMap = new Map(); @@ -270,6 +285,11 @@ class ModelFile extends Decorated { let classDeclaration = this.declarations[n]; classDeclaration.validate(); } + + // Set up the decorators, we fake a name for the model element + this.decorated = new Decorated(this, this.ast); + this.decorated.process(); + this.decorated.validate(); } /** @@ -361,7 +381,6 @@ class ModelFile extends Decorated { * For primitive types the type name is returned. * @param {string} type - a FQN or short type name * @return {string | ClassDeclaration} the class declaration for the type or null. - * @private */ getType(type) { // is the type a primitive? @@ -591,15 +610,22 @@ class ModelFile extends Decorated { /** * Get the instances of a given type in this ModelFile - * @param {Function} type - the type of the declaration + * @param {Function|string} type - the type of the declaration + * (either a short metamodel name or a constructor function for a type) * @return {Object[]} the ClassDeclaration defined in the model file */ getDeclarations(type) { let result = []; for(let n=0; n < this.declarations.length; n++) { let declaration = this.declarations[n]; - if(declaration instanceof type) { - result.push(declaration); + if(type instanceof Function) { + if(declaration instanceof type) { + result.push(declaration); + } + } else { + if(declaration.declarationKind() === type) { + result.push(declaration); + } } } diff --git a/packages/concerto-core/src/introspect/participantdeclaration.js b/packages/concerto-core/src/introspect/participantdeclaration.js index 1050fc5031..1ae121c31d 100644 --- a/packages/concerto-core/src/introspect/participantdeclaration.js +++ b/packages/concerto-core/src/introspect/participantdeclaration.js @@ -14,7 +14,7 @@ 'use strict'; -const IdentifiedDeclaration = require('./identifieddeclaration'); +const ClassDeclaration = require('./classdeclaration'); // Types needed for TypeScript generation. /* eslint-disable no-unused-vars */ @@ -31,7 +31,7 @@ if (global === undefined) { * @class * @memberof module:concerto-core */ -class ParticipantDeclaration extends IdentifiedDeclaration { +class ParticipantDeclaration extends ClassDeclaration { /** * Create an ParticipantDeclaration. * @param {ModelFile} modelFile the ModelFile for this class @@ -41,15 +41,6 @@ class ParticipantDeclaration extends IdentifiedDeclaration { constructor(modelFile, ast) { super(modelFile, ast); } - - /** - * Returns the kind of declaration - * - * @return {string} what kind of declaration this is - */ - declarationKind() { - return 'ParticipantDeclaration'; - } } module.exports = ParticipantDeclaration; diff --git a/packages/concerto-core/src/introspect/property.js b/packages/concerto-core/src/introspect/property.js index db041f0d0b..acd26182b5 100644 --- a/packages/concerto-core/src/introspect/property.js +++ b/packages/concerto-core/src/introspect/property.js @@ -17,7 +17,6 @@ const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); const ModelUtil = require('../modelutil'); -const IllegalModelException = require('./illegalmodelexception'); const Decorated = require('./decorated'); // Types needed for TypeScript generation. @@ -25,14 +24,13 @@ const Decorated = require('./decorated'); /* istanbul ignore next */ if (global === undefined) { const ClassDeclaration = require('./classdeclaration'); - const ModelFile = require('./modelfile'); } /* eslint-enable no-unused-vars */ /** * Property representing an attribute of a class declaration, - * either a Field or a Relationship. + * either a Field or a Relationship. Properties may be array or be optional. * * @class * @memberof module:concerto-core @@ -45,21 +43,11 @@ class Property extends Decorated { * @throws {IllegalModelException} */ constructor(parent, ast) { - super(ast); + super(parent.getModelFile(), ast); this.parent = parent; this.process(); } - /** - * Returns the ModelFile that defines this class. - * - * @public - * @return {ModelFile} the owning ModelFile - */ - getModelFile() { - return this.parent.getModelFile(); - } - /** * Returns the owner of this property * @return {ClassDeclaration} the parent class declaration @@ -76,43 +64,34 @@ class Property extends Decorated { process() { super.process(); - if (!ModelUtil.isValidIdentifier(this.ast.name)){ - throw new IllegalModelException(`Invalid property name '${this.ast.name}'`, this.modelFile, this.ast.location); - } - - this.name = this.ast.name; - this.decorator = null; + this.propertyType = null; - if(!this.name) { - throw new Error('No name for type ' + JSON.stringify(this.ast)); - } - - switch (this.ast.$class) { + switch (this.getMetaType()) { case `${MetaModelNamespace}.EnumProperty`: break; case `${MetaModelNamespace}.BooleanProperty`: - this.type = 'Boolean'; + this.propertyType = 'Boolean'; break; case `${MetaModelNamespace}.DateTimeProperty`: - this.type = 'DateTime'; + this.propertyType = 'DateTime'; break; case `${MetaModelNamespace}.DoubleProperty`: - this.type = 'Double'; + this.propertyType = 'Double'; break; case `${MetaModelNamespace}.IntegerProperty`: - this.type = 'Integer'; + this.propertyType = 'Integer'; break; case `${MetaModelNamespace}.LongProperty`: - this.type = 'Long'; + this.propertyType = 'Long'; break; case `${MetaModelNamespace}.StringProperty`: - this.type = 'String'; + this.propertyType = 'String'; break; case `${MetaModelNamespace}.ObjectProperty`: - this.type = this.ast.type ? this.ast.type.name : null; + this.propertyType = this.ast.type ? this.ast.type.name : null; break; case `${MetaModelNamespace}.RelationshipProperty`: - this.type = this.ast.type.name; + this.propertyType = this.ast.type.name; break; } this.array = false; @@ -127,6 +106,10 @@ class Property extends Decorated { else { this.optional = false; } + + if(!this.ast.name) { + throw new Error(`Property of type ${this.propertyType} must have a name.`); + } } /** @@ -138,30 +121,35 @@ class Property extends Decorated { validate(classDecl) { super.validate(); - if(this.type) { - classDecl.getModelFile().resolveType( 'property ' + this.getFullyQualifiedName(), this.type); + if(this.propertyType) { + classDecl.getModelFile().resolveType( 'property ' + this.getFullyQualifiedName(), this.propertyType); } } /** - * Returns the name of a property - * @return {string} the name of this field + * Returns the type of a property. + * @deprecated replaced by getPropertyType() + * @return {string} the type of this property */ - getName() { - return this.name; + getType() { + return this.getPropertyType(); } /** - * Returns the type of a property - * @return {string} the type of this field + * Returns the type of a property. This will return either: a primitive type + * name (String, Boolean, Integer etc) or the name of a non-primitive type, + * or will return null if this is an enum property. + * + * Note this is NOT the same as getMetaType() which returns the meta type for the property. + * @return {string|null} the type of this property or null if this is an enum property */ - getType() { - return this.type; + getPropertyType() { + return this.propertyType; } /** * Returns true if the field is optional - * @return {boolean} true if the field is optional + * @return {boolean} true if the property is optional */ isOptional() { return this.optional; @@ -173,7 +161,7 @@ class Property extends Decorated { */ getFullyQualifiedTypeName() { if(this.isPrimitive()) { - return this.type; + return this.propertyType; } const parent = this.getParent(); @@ -184,9 +172,9 @@ class Property extends Decorated { if(!modelFile) { throw new Error('Parent of property ' + this.name + ' does not have a ModelFile!'); } - const result = modelFile.getFullyQualifiedTypeName(this.type); + const result = modelFile.getFullyQualifiedTypeName(this.propertyType); if(!result) { - throw new Error('Failed to find fully qualified type name for property ' + this.name + ' with type ' + this.type ); + throw new Error('Failed to find fully qualified type name for property ' + this.name + ' with type ' + this.propertyType ); } return result; @@ -197,15 +185,7 @@ class Property extends Decorated { * @return {string} the fully qualified name of this property */ getFullyQualifiedName() { - return this.getParent().getFullyQualifiedName() + '.' + this.getName(); - } - - /** - * Returns the namespace of the parent of this property - * @return {string} the namespace of the parent of this property - */ - getNamespace() { - return this.getParent().getNamespace(); + return `${this.getParent().getFullyQualifiedName()}.${this.getName()}`; } /** @@ -216,17 +196,25 @@ class Property extends Decorated { return this.array; } - /** * Returns true if the field is declared as an enumerated value + * @deprecated replaced by isPropertyEnum() * @return {boolean} true if the property is an enumerated value */ isTypeEnum() { + return this.isPropertyEnum(); + } + + /** + * Returns true if the field is declared as an enumerated value + * @return {boolean} true if the property is an enumerated value + */ + isPropertyEnum() { if(this.isPrimitive()) { return false; } else { - const type = this.getParent().getModelFile().getType(this.getType()); + const type = this.getModelFile().getType(this.getPropertyType()); return type.isEnum(); } } @@ -236,7 +224,7 @@ class Property extends Decorated { * @return {boolean} true if the property is a primitive type. */ isPrimitive() { - return ModelUtil.isPrimitiveType(this.getType()); + return ModelUtil.isPrimitiveType(this.getPropertyType()); } } diff --git a/packages/concerto-core/src/introspect/relationshipdeclaration.js b/packages/concerto-core/src/introspect/relationshipproperty.js similarity index 86% rename from packages/concerto-core/src/introspect/relationshipdeclaration.js rename to packages/concerto-core/src/introspect/relationshipproperty.js index 02dbeb8393..06c5d290ec 100644 --- a/packages/concerto-core/src/introspect/relationshipdeclaration.js +++ b/packages/concerto-core/src/introspect/relationshipproperty.js @@ -34,7 +34,7 @@ if (global === undefined) { * @class * @memberof module:concerto-core */ -class RelationshipDeclaration extends Property { +class RelationshipProperty extends Property { /** * Create a Relationship. * @param {ClassDeclaration} parent - The owner of this property @@ -54,14 +54,14 @@ class RelationshipDeclaration extends Property { validate(classDecl) { super.validate(classDecl); // relationship cannot point to primitive types - if(!this.getType()) { + if(!this.getPropertyType()) { throw new IllegalModelException('Relationship must have a type', classDecl.getModelFile(), this.ast.location); } let classDeclaration = null; // you can't have a relationship with a primitive... - if(ModelUtil.isPrimitiveType(this.getType())) { + if(ModelUtil.isPrimitiveType(this.getPropertyType())) { throw new IllegalModelException('Relationship ' + this.getName() + ' cannot be to the primitive type ' + this.getType(), classDecl.getModelFile(), this.ast.location ); } else { let namespace = this.getParent().getNamespace(); @@ -69,7 +69,7 @@ class RelationshipDeclaration extends Property { // we first try to get the type from our own model file // because during validate we have not yet been added to the model manager if(namespace === ModelUtil.getNamespace(this.getFullyQualifiedTypeName())) { - classDeclaration = this.getParent().getModelFile().getType(this.getType()); + classDeclaration = this.getParent().getModelFile().getType(this.getPropertyType()); } else { // otherwise we have to use the modelmanager to try to load @@ -97,17 +97,8 @@ class RelationshipDeclaration extends Property { * @return {String} the string version of the property. */ toString() { - return 'RelationshipDeclaration {name=' + this.name + ', type=' + this.getFullyQualifiedTypeName() + ', array=' + this.array + ', optional=' + this.optional +'}'; - } - - /** - * Returns true if this class is the definition of a relationship. - * - * @return {boolean} true if the class is a relationship - */ - isRelationship() { - return true; + return 'RelationshipProperty {name=' + this.name + ', type=' + this.getFullyQualifiedTypeName() + ', array=' + this.array + ', optional=' + this.optional +'}'; } } -module.exports = RelationshipDeclaration; +module.exports = RelationshipProperty; diff --git a/packages/concerto-core/src/introspect/scalardeclaration.js b/packages/concerto-core/src/introspect/scalardeclaration.js index 3fab478621..1219a8bbca 100644 --- a/packages/concerto-core/src/introspect/scalardeclaration.js +++ b/packages/concerto-core/src/introspect/scalardeclaration.js @@ -17,7 +17,6 @@ const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); const Declaration = require('./declaration'); -const IllegalModelException = require('./illegalmodelexception'); const NumberValidator = require('./numbervalidator'); const StringValidator = require('./stringvalidator'); @@ -26,7 +25,6 @@ const StringValidator = require('./stringvalidator'); /* istanbul ignore next */ if (global === undefined) { const Validator = require('./validator'); - const ClassDeclaration = require('./classdeclaration'); } /* eslint-enable no-unused-vars */ @@ -51,30 +49,24 @@ class ScalarDeclaration extends Declaration { process() { super.process(); - this.superType = null; - this.superTypeDeclaration = null; - this.idField = null; - this.timestamped = false; - this.abstract = false; this.validator = null; + this.scalarType = null; if (this.ast.$class === `${MetaModelNamespace}.BooleanScalar`) { - this.type = 'Boolean'; + this.scalarType = 'Boolean'; } else if (this.ast.$class === `${MetaModelNamespace}.IntegerScalar`) { - this.type = 'Integer'; + this.scalarType = 'Integer'; } else if (this.ast.$class === `${MetaModelNamespace}.LongScalar`) { - this.type = 'Long'; + this.scalarType = 'Long'; } else if (this.ast.$class === `${MetaModelNamespace}.DoubleScalar`) { - this.type = 'Double'; + this.scalarType = 'Double'; } else if (this.ast.$class === `${MetaModelNamespace}.StringScalar`) { - this.type = 'String'; + this.scalarType = 'String'; } else if (this.ast.$class === `${MetaModelNamespace}.DateTimeScalar`) { - this.type = 'DateTime'; - } else { - this.type = null; + this.scalarType = 'DateTime'; } - switch(this.getType()) { + switch(this.scalarType) { case 'Integer': case 'Double': case 'Long': @@ -97,87 +89,12 @@ class ScalarDeclaration extends Declaration { } /** - * Semantic validation of the structure of this class. Subclasses should - * override this method to impose additional semantic constraints on the - * contents/relations of fields. - * - * @throws {IllegalModelException} - * @protected - */ - validate() { - super.validate(); - - const declarations = this.getModelFile().getAllDeclarations(); - const declarationNames = declarations.map( - d => d.getFullyQualifiedName() - ); - const uniqueNames = new Set(declarationNames); - - if (uniqueNames.size !== declarations.length) { - const duplicateElements = declarationNames.filter( - (item, index) => declarationNames.indexOf(item) !== index - ); - throw new IllegalModelException( - `Duplicate class name ${duplicateElements[0]}` - ); - } - } - - /** - * Returns false as scalars are never identified. - * @returns {Boolean} false as scalars are never identified - * @deprecated - */ - isIdentified() { - return false; - } - - /** - * Returns false as scalars are never identified. - * @returns {Boolean} false as scalars are never identified - * @deprecated - */ - isSystemIdentified() { - return false; - } - - /** - * Returns null as scalars are never identified. - * @return {string} as scalars are never identified - * @deprecated - */ - getIdentifierFieldName() { - return null; - } - - /** - * Returns the FQN of the super type for this class or null if this - * class does not have a super type. + * Returns the underlying primitive type of this scalar * - * @return {string} the FQN name of the super type or null + * @return {string} the type of the scalar (String, Integer, Long etc.) */ - getType() { - return this.type; - } - - /** - * Returns the FQN of the super type for this class or null if this - * class does not have a super type. - * - * @return {string} the FQN name of the super type or null - * @deprecated - */ - getSuperType() { - return null; - } - - /** - * Get the super type class declaration for this class. - * @return {ClassDeclaration} the super type declaration, or null if there is no super type. - * @deprecated - */ - getSuperTypeDeclaration() { - return null; + getScalarType() { + return this.scalarType; } /** @@ -206,78 +123,9 @@ class ScalarDeclaration extends Declaration { * @return {String} the string representation of the class */ toString() { - return 'ScalarDeclaration {id=' + this.getFullyQualifiedName() + '}'; + return 'ScalarDeclaration {id=' + this.getFullyQualifiedName() + + ' scalarType=' + this.scalarType + '}'; } - - /** - * Returns true if this class is abstract. - * - * @return {boolean} true if the class is abstract - * @deprecated - */ - isAbstract() { - return true; - } - - /** - * Returns true if this class is the definition of a scalar declaration. - * - * @return {boolean} true if the class is a scalar - */ - isScalarDeclaration() { - return true; - } - - /** - * Returns true if this class is the definition of an asset. - * - * @return {boolean} true if the class is an asset - * @deprecated - */ - isAsset() { - return false; - } - - /** - * Returns true if this class is the definition of a participant. - * - * @return {boolean} true if the class is a participant - * @deprecated - */ - isParticipant() { - return false; - } - - /** - * Returns true if this class is the definition of a transaction. - * - * @return {boolean} true if the class is a transaction - * @deprecated - */ - isTransaction() { - return false; - } - - /** - * Returns true if this class is the definition of an event. - * - * @return {boolean} true if the class is an event - * @deprecated - */ - isEvent() { - return false; - } - - /** - * Returns true if this class is the definition of a concept. - * - * @return {boolean} true if the class is a concept - * @deprecated - */ - isConcept() { - return false; - } - } module.exports = ScalarDeclaration; diff --git a/packages/concerto-core/src/introspect/transactiondeclaration.js b/packages/concerto-core/src/introspect/transactiondeclaration.js index b7b43e46fb..afd996aaeb 100644 --- a/packages/concerto-core/src/introspect/transactiondeclaration.js +++ b/packages/concerto-core/src/introspect/transactiondeclaration.js @@ -14,7 +14,7 @@ 'use strict'; -const IdentifiedDeclaration = require('./identifieddeclaration'); +const ClassDeclaration = require('./classdeclaration'); // Types needed for TypeScript generation. /* eslint-disable no-unused-vars */ @@ -31,7 +31,7 @@ if (global === undefined) { * @class * @memberof module:concerto-core */ -class TransactionDeclaration extends IdentifiedDeclaration { +class TransactionDeclaration extends ClassDeclaration { /** * Create an TransactionDeclaration. * @param {ModelFile} modelFile the ModelFile for this class @@ -41,15 +41,6 @@ class TransactionDeclaration extends IdentifiedDeclaration { constructor(modelFile, ast) { super(modelFile, ast); } - - /** - * Returns the kind of declaration - * - * @return {string} what kind of declaration this is - */ - declarationKind() { - return 'TransactionDeclaration'; - } } module.exports = TransactionDeclaration; diff --git a/packages/concerto-core/src/messages/en.json b/packages/concerto-core/src/messages/en.json index 910b20822e..f1bb58d299 100644 --- a/packages/concerto-core/src/messages/en.json +++ b/packages/concerto-core/src/messages/en.json @@ -65,6 +65,8 @@ "resourcevalidator-fieldtypeviolation": "Model violation in the \"{resourceId}\" instance. The field \"{propertyName}\" has a value of \"{value}\" (type of value: \"{typeOfValue}\"). Expected type of value: \"{fieldType}\".", "resourcevalidator-missingrequiredproperty": "The instance \"{resourceId}\" is missing the required field \"{fieldName}\".", "resourcevalidator-invalidenumvalue": "Model violation in the \"{resourceId}\" instance. Invalid enum value of \"{value}\" for the field \"{fieldName}\".", + "resourcevalidator-invalidmapkey": "Model violation in the \"{resourceId}\" instance. Invalid map key of \"{value}\".", + "resourcevalidator-invalidmapvalue": "Model violation in the \"{resourceId}\" instance. Invalid map value of \"{value}\".", "resourcevalidator-abstractclass": "The class \"{className}\" is abstract and should not contain an instance.", "resourcevalidator-undeclaredfield": "Instance \"{resourceId}\" has a property named \"{propertyName}\", which is not declared in \"{fullyQualifiedTypeName}\".", "resourcevalidator-invalidfieldassignment": "Instance \"{resourceId}\" has a property \"{propertyName}\" with type \"{objectType}\" that is not derived from \"{fieldType}\".", diff --git a/packages/concerto-core/src/modelutil.js b/packages/concerto-core/src/modelutil.js index 887520700a..069225116c 100644 --- a/packages/concerto-core/src/modelutil.js +++ b/packages/concerto-core/src/modelutil.js @@ -338,6 +338,34 @@ class ModelUtil { `${MetaModelNamespace}.ObjectMapValueType` ].includes(value.$class); } + + /** + * Visits the model element. + * @param {ModelElement} modelElement the model element + * @param {*} parameters the visitor params + * @param {*} visitor the visitor + * @returns {*} result of visit + */ + static dispatch(modelElement, parameters, visitor) { + if(!modelElement.isMap) { + throw new Error('Model element is invalid.'); + } + if (modelElement.isMap()) { + return visitor.visitMapDeclaration(modelElement, parameters); + } else if (modelElement.isEnum()) { + return visitor.visitEnumDeclaration(modelElement, parameters); + } else if (modelElement.isDeclaration()) { + return visitor.visitClassDeclaration(modelElement, parameters); + } else if (modelElement.isRelationship()) { + return visitor.visitRelationshipDeclaration(modelElement, parameters); + } else if (modelElement.isField()) { + const field = modelElement.isTypeScalar?.() ? modelElement.getScalarField() : modelElement; + return visitor.visitField(field, parameters); + } else { + throw new Error(`Unrecognised model element: ${modelElement.toString()}` ); + } + } } + module.exports = ModelUtil; diff --git a/packages/concerto-core/src/serializer.js b/packages/concerto-core/src/serializer.js index 817ba5d6ec..1f84ae7212 100644 --- a/packages/concerto-core/src/serializer.js +++ b/packages/concerto-core/src/serializer.js @@ -173,7 +173,7 @@ class Serializer { resource = this.factory.newConcept(classDeclaration.getNamespace(), classDeclaration.getName(), jsonObject[classDeclaration.getIdentifierFieldName()] ); - } else if (classDeclaration.isMapDeclaration?.()) { + } else if (classDeclaration.isMap()) { throw new Error('Attempting to create a Map declaration is not supported.'); } else if (classDeclaration.isEnum()) { throw new Error('Attempting to create an ENUM declaration is not supported.'); @@ -190,7 +190,7 @@ class Serializer { parameters.resourceStack = new TypedStack(resource); parameters.modelManager = this.modelManager; parameters.factory = this.factory; - const populator = new JSONPopulator(options.acceptResourcesForRelationships === true, false, options.utcOffset, options.strictQualifiedDateTimes === true); + const populator = new JSONPopulator(options.acceptResourcesForRelationships === true, options.utcOffset, options.strictQualifiedDateTimes === true); classDeclaration.accept(populator, parameters); // validate the resource against the model diff --git a/packages/concerto-core/src/serializer/instancegenerator.js b/packages/concerto-core/src/serializer/instancegenerator.js index 1055eaf8ac..1277bb9cfe 100644 --- a/packages/concerto-core/src/serializer/instancegenerator.js +++ b/packages/concerto-core/src/serializer/instancegenerator.js @@ -15,6 +15,7 @@ 'use strict'; const Util = require('../util'); +const ModelUtil = require('../modelutil'); const Globalize = require('../globalize'); /** @@ -35,19 +36,7 @@ class InstanceGenerator { * @private */ visit(thing, parameters) { - if (thing.isClassDeclaration?.()) { - return this.visitClassDeclaration(thing, parameters); - } else if (thing.isMapDeclaration?.()) { - return this.visitMapDeclaration(thing, parameters); - } else if (thing.isRelationship?.()) { - return this.visitRelationshipDeclaration(thing, parameters); - } else if (thing.isTypeScalar?.()) { - return this.visitField(thing.getScalarField(), parameters); - } else if (thing.isField?.()) { - return this.visitField(thing, parameters); - } else { - throw new Error('Unrecognised ' + JSON.stringify(thing) ); - } + return ModelUtil.dispatch(thing, parameters, this); } /** @@ -213,7 +202,7 @@ class InstanceGenerator { /** * Visitor design pattern - * @param {RelationshipDeclaration} relationshipDeclaration - the object being visited + * @param {RelationshipProperty} relationshipDeclaration - the object being visited * @param {Object} parameters - the parameter * @return {Relationship} the result of visiting * @private diff --git a/packages/concerto-core/src/serializer/jsongenerator.js b/packages/concerto-core/src/serializer/jsongenerator.js index f211a897d8..e8b970d149 100644 --- a/packages/concerto-core/src/serializer/jsongenerator.js +++ b/packages/concerto-core/src/serializer/jsongenerator.js @@ -61,19 +61,7 @@ class JSONGenerator { * @private */ visit(thing, parameters) { - if (thing.isClassDeclaration?.()) { - return this.visitClassDeclaration(thing, parameters); - } else if (thing.isRelationship?.()) { - return this.visitRelationshipDeclaration(thing, parameters); - }else if (thing.isMapDeclaration?.()) { - return this.visitMapDeclaration(thing, parameters); - } else if (thing.isTypeScalar?.()) { - return this.visitField(thing.getScalarField(), parameters); - } else if (thing.isField?.()) { - return this.visitField(thing, parameters); - } else { - throw new Error('Unrecognised ' + JSON.stringify(thing)); - } + return ModelUtil.dispatch(thing, parameters, this); } /** @@ -86,30 +74,24 @@ class JSONGenerator { visitMapDeclaration(mapDeclaration, parameters) { const obj = parameters.stack.pop(); - // initialise Map with $class property - let map = new Map(); + const map = new Map(); + const mapKeyField = mapDeclaration.getKey().toField(); + const mapValueField = mapDeclaration.getValue().toField(); obj.forEach((value, key) => { - // don't serialize System Properties, other than $class if(ModelUtil.isSystemProperty(key)) { return; } - // Key is always a string, but value might be a ValidatedResource. - if (typeof value === 'object') { - let decl = mapDeclaration.getModelFile() - .getAllDeclarations() - .find(decl => decl.name === value.getType()); - - // convert declaration to JSON representation - parameters.stack.push(value); - const jsonValue = decl.accept(this, parameters); - - value = jsonValue; - } + // convert the key + parameters.stack.push(key); + const keyJson = mapKeyField.accept(this, parameters); - map.set(key, value); + // convert the value + parameters.stack.push(value); + const valueJson = mapValueField.accept(this, parameters); + map.set(keyJson, valueJson); }); return Object.fromEntries(map); @@ -195,6 +177,7 @@ class JSONGenerator { result = mapDeclaration.accept(this, parameters); } else { + // concepts parameters.stack.push(obj); const classDeclaration = parameters.modelManager.getType(obj.getFullyQualifiedType()); result = classDeclaration.accept(this, parameters); @@ -233,7 +216,7 @@ class JSONGenerator { /** * Visitor design pattern - * @param {RelationshipDeclaration} relationshipDeclaration - the object being visited + * @param {RelationshipProperty} relationshipDeclaration - the object being visited * @param {Object} parameters - the parameter * @return {Object} the result of visiting or null * @private @@ -286,7 +269,7 @@ class JSONGenerator { /** * Returns the persistent format for a relationship. - * @param {RelationshipDeclaration} relationshipDeclaration - the relationship being persisted + * @param {RelationshipProperty} relationshipDeclaration - the relationship being persisted * @param {Identifiable} relationshipOrResource - the relationship or the resource * @returns {string} the text to use to persist the relationship */ diff --git a/packages/concerto-core/src/serializer/jsonpopulator.js b/packages/concerto-core/src/serializer/jsonpopulator.js index 891bf9c375..10a9d05c76 100644 --- a/packages/concerto-core/src/serializer/jsonpopulator.js +++ b/packages/concerto-core/src/serializer/jsonpopulator.js @@ -29,6 +29,14 @@ dayjs.extend(minMax); const duration = require('dayjs/plugin/duration'); dayjs.extend(duration); +// Types needed for TypeScript generation. +/* eslint-disable no-unused-vars */ +/* istanbul ignore next */ +if (global === undefined) { + const ModelElement = require('../introspect/modelelement'); +} +/* eslint-enable no-unused-vars */ + /** * Get all properties on a resource object that both have a value and are not system properties. * @param {Object} resourceData JSON object representation of a resource. @@ -94,12 +102,11 @@ class JSONPopulator { * Constructor. * @param {boolean} [acceptResourcesForRelationships] Permit resources in the * place of relationships, false by default. - * @param {boolean} [ergo] - Deprecated - This is a dummy parameter to avoid breaking any consumers. It will be removed in a future release. * @param {number} [utcOffset] - UTC Offset for DateTime values. * @param {number} [strictQualifiedDateTimes] - Only allow fully-qualified date-times with offsets. */ - constructor(acceptResourcesForRelationships, ergo, utcOffset, strictQualifiedDateTimes) { + constructor(acceptResourcesForRelationships, utcOffset, strictQualifiedDateTimes) { this.acceptResourcesForRelationships = acceptResourcesForRelationships; this.utcOffset = utcOffset || 0; // Defaults to UTC this.strictQualifiedDateTimes = strictQualifiedDateTimes; @@ -111,27 +118,14 @@ class JSONPopulator { /** * Visitor design pattern - * @param {Object} thing - the object being visited + * @param {ModelElement} modelElement - the model element being visited * @param {Object} parameters - the parameter * @return {Object} the result of visiting or null * @private */ - visit(thing, parameters = {}) { + visit(modelElement, parameters = {}) { parameters.path ?? (parameters.path = new TypedStack('$')); - - if (thing.isClassDeclaration?.()) { - return this.visitClassDeclaration(thing, parameters); - } else if (thing.isMapDeclaration?.()) { - return this.visitMapDeclaration(thing, parameters); - } else if (thing.isRelationship?.()) { - return this.visitRelationshipDeclaration(thing, parameters); - } else if (thing.isTypeScalar?.()) { - return this.visitField(thing.getScalarField(), parameters); - } else if (thing.isField?.()) { - return this.visitField(thing, parameters); - } else { - throw new Error('Unrecognised ' + JSON.stringify(thing) ); - } + return ModelUtil.dispatch(modelElement, parameters, this); } /** @@ -177,57 +171,26 @@ class JSONPopulator { getAssignableProperties(jsonObj, mapDeclaration); jsonObj = new Map(Object.entries(jsonObj)); - - let map = new Map(); + const map = new Map(); + const mapKeyField = mapDeclaration.getKey().toField(); + const mapValueField = mapDeclaration.getValue().toField(); jsonObj.forEach((value, key) => { + // convert the key + parameters.jsonStack.push(key); + parameters.path.push(key); + const keyJson = mapKeyField.accept(this, parameters); - if (key === '$class') { - map.set(key, value); - return; - } - - if (!ModelUtil.isPrimitiveType(mapDeclaration.getKey().getType())) { - key = this.processMapType(mapDeclaration, parameters, key, mapDeclaration.getKey().getType()); - } - - if (!ModelUtil.isPrimitiveType(mapDeclaration.getValue().getType())) { - value = this.processMapType(mapDeclaration, parameters, value, mapDeclaration.getValue().getType()); - } - - map.set(key, value); + // convert the value + parameters.jsonStack.push(value); + parameters.path.push(key); + const valueJson = mapValueField.accept(this, parameters); + map.set(keyJson, valueJson); }); return map; } - /** - * Visitor design pattern - * @param {MapDeclaration} mapDeclaration - the object being visited - * @param {Object} parameters - the parameter - * @param {Object} value - the key or value belonging to the Map Entry. - * @param {Object} type - the Type associated with the Key or Value Map Entry. - * @return {Object} value - the key or value belonging to the Map Entry. - * @private - */ - processMapType(mapDeclaration, parameters, value, type) { - let decl = mapDeclaration.getModelFile() - .getAllDeclarations() - .find(decl => decl.name === type); - - // if its a ClassDeclaration, populate the Concept. - if (decl?.isClassDeclaration()) { - let subResource = parameters.factory.newConcept(decl.getNamespace(), - decl.getName(), decl.getIdentifierFieldName() ); - - parameters.jsonStack.push(value); - parameters.resourceStack.push(subResource); - return decl.accept(this, parameters); - } - // otherwise its a scalar value, we only need to return the primitve value of the scalar. - return value; - } - /** * Visitor design pattern * @param {Field} field - the object being visited @@ -383,7 +346,7 @@ class JSONPopulator { /** * Visitor design pattern - * @param {RelationshipDeclaration} relationshipDeclaration - the object being visited + * @param {RelationshipProperty} relationshipDeclaration - the object being visited * @param {Object} parameters - the parameter * @return {Object} the result of visiting or null * @private diff --git a/packages/concerto-core/src/serializer/resourcevalidator.js b/packages/concerto-core/src/serializer/resourcevalidator.js index 711fd6cefd..8a813fc810 100644 --- a/packages/concerto-core/src/serializer/resourcevalidator.js +++ b/packages/concerto-core/src/serializer/resourcevalidator.js @@ -67,19 +67,7 @@ class ResourceValidator { * @private */ visit(thing, parameters) { - if (thing.isEnum?.()) { - return this.visitEnumDeclaration(thing, parameters); - } else if (thing.isClassDeclaration?.()) { - return this.visitClassDeclaration(thing, parameters); - } else if (thing.isMapDeclaration?.()) { - return this.visitMapDeclaration(thing, parameters); - }else if (thing.isRelationship?.()) { - return this.visitRelationshipDeclaration(thing, parameters); - } else if (thing.isTypeScalar?.()) { - return this.visitField(thing.getScalarField(), parameters); - } else if (thing.isField?.()) { - return this.visitField(thing, parameters); - } + return ModelUtil.dispatch(thing, parameters, this); } /** @@ -110,61 +98,6 @@ class ResourceValidator { return null; } - - /** - * Check a Type that is declared as a Map Type. - * @param {Object} type - the type in scope for validation, can be MapTypeKey or MapTypeValue - * @param {Object} value - the object being validated - * @param {Object} parameters - the parameter - * @param {Map} mapDeclaration - the object being visited - * @private - */ - checkMapType(type, value, parameters, mapDeclaration, ) { - - if (!ModelUtil.isPrimitiveType(type.getType())) { - - // thing might be a Concept, Scalar String, Scalar DateTime - let thing = mapDeclaration.getModelFile() - .getAllDeclarations() - .find(decl => decl.name === type.getType()); - - // if Key or Value is Scalar, get the Base Type of the Scalar for primitive validation. - if (ModelUtil.isScalar(mapDeclaration.getKey())) { - type = thing.getType(); - } - - if (thing?.isClassDeclaration?.()) { - parameters.stack.push(value); - thing.accept(this, parameters); - return; - } - } else { - // otherwise its a primitive - type = type.getType(); - - } - - // validate the primitive - switch(type) { - case 'String': - if (typeof value !== 'string') { - throw new Error(`Model violation in ${mapDeclaration.getFullyQualifiedName()}. Expected Type of String but found '${value}' instead.`); - } - break; - case 'DateTime': - if (!dayjs.utc(value).isValid()) { - throw new Error(`Model violation in ${mapDeclaration.getFullyQualifiedName()}. Expected Type of DateTime but found '${value}' instead.`); - } - break; - case 'Boolean': - if (typeof value !== 'boolean') { - const type = typeof value; - throw new Error(`Model violation in ${mapDeclaration.getFullyQualifiedName()}. Expected Type of Boolean but found ${type} instead, for value '${value}'.`); - } - break; - } - } - /** * Visitor design pattern * @@ -182,12 +115,17 @@ class ResourceValidator { throw new Error('Expected a Map, but found ' + JSON.stringify(obj)); } + const mapKeyField = mapDeclaration.getKey().toField(); + const mapValueField = mapDeclaration.getValue().toField(); + obj.forEach((value, key) => { if (!ModelUtil.isSystemProperty(key)) { - // Validate Key - this.checkMapType(mapDeclaration.getKey(), key, parameters, mapDeclaration); - // Validate Value - this.checkMapType(mapDeclaration.getValue(), value, parameters, mapDeclaration); + // validate key + parameters.stack.push(key); + mapKeyField.accept(this,parameters); + // validate value + parameters.stack.push(value); + mapValueField.accept(this,parameters); } }); @@ -302,7 +240,7 @@ class ResourceValidator { ResourceValidator.reportFieldTypeViolation(parameters.rootResourceIdentifier, propName, obj, field); } - if(field.isTypeEnum()) { + if(field.isPropertyEnum()) { this.checkEnum(obj, field,parameters); } else { @@ -444,7 +382,7 @@ class ResourceValidator { /** * Visitor design pattern - * @param {RelationshipDeclaration} relationshipDeclaration - the object being visited + * @param {RelationshipProperty} relationshipDeclaration - the object being visited * @param {Object} parameters - the parameter * @return {Object} the result of visiting or null * @private @@ -556,7 +494,7 @@ class ResourceValidator { /** * Throw a new error for a model violation. * @param {string} id - the identifier of this instance. - * @param {RelationshipDeclaration} relationshipDeclaration - the declaration of the class + * @param {RelationshipProperty} relationshipDeclaration - the declaration of the class * @param {Object} value - the value of the field. * @private */ diff --git a/packages/concerto-core/test/decoratormanager.js b/packages/concerto-core/test/decoratormanager.js index ef8034b71b..92c69f0d31 100644 --- a/packages/concerto-core/test/decoratormanager.js +++ b/packages/concerto-core/test/decoratormanager.js @@ -63,7 +63,7 @@ describe('DecoratorManager', () => { describe('#validate', function() { it('should support syntax validation', async function() { const dcs = fs.readFileSync('./test/data/decoratorcommands/web.json', 'utf-8'); - const validationModelManager = DecoratorManager.validate( JSON.parse(dcs)); + const validationModelManager = DecoratorManager.validate(JSON.parse(dcs)); validationModelManager.should.not.be.null; }); diff --git a/packages/concerto-core/test/introspect/classdeclaration.js b/packages/concerto-core/test/introspect/classdeclaration.js index aee2ce838e..b1a867e466 100644 --- a/packages/concerto-core/test/introspect/classdeclaration.js +++ b/packages/concerto-core/test/introspect/classdeclaration.js @@ -47,7 +47,7 @@ describe('ClassDeclaration', () => { describe('#constructor', () => { - it('should throw if ast contains invalid type', () => { + it('should throw if ast does not contain $class', () => { (() => { new ClassDeclaration(modelFile, { name: 'suchName', @@ -55,16 +55,29 @@ describe('ClassDeclaration', () => { $class: 'noSuchType' }] }); + }).should.throw(/Invalid ModelElement; must have a \$class/); + }); + + it('should throw if ast contains invalid property type', () => { + (() => { + new ClassDeclaration(modelFile, { + $class: `${MetaModelNamespace}.ConceptDeclaration`, + name: 'suchName', + properties: [{ + $class: 'noSuchType' + }] + }); }).should.throw(/Unrecognised model element/); }); it('should throw for a bad identifier', () => { (() => { new ClassDeclaration(modelFile, { + $class: `${MetaModelNamespace}.ConceptDeclaration`, name: '2nd', properties: [] }); - }).should.throw(/Invalid class name '2nd'/); + }).should.throw(/Invalid model element name '2nd'/); }); }); @@ -74,21 +87,21 @@ describe('ClassDeclaration', () => { let asset = introspectUtils.loadLastDeclaration('test/data/parser/classdeclaration.dupeassetname.cto', AssetDeclaration); (() => { asset.validate(); - }).should.throw(/Duplicate class/); + }).should.throw(/Duplicate declaration/); }); it('should throw when transaction name is duplicted in a modelfile', () => { let asset = introspectUtils.loadLastDeclaration('test/data/parser/classdeclaration.dupetransactionname.cto', TransactionDeclaration); (() => { asset.validate(); - }).should.throw(/Duplicate class/); + }).should.throw(/Duplicate declaration/); }); it('should throw when participant name is duplicted in a modelfile', () => { let asset = introspectUtils.loadLastDeclaration('test/data/parser/classdeclaration.dupeparticipantname.cto', ParticipantDeclaration); (() => { asset.validate(); - }).should.throw(/Duplicate class/); + }).should.throw(/Duplicate declaration/); }); it('should throw when an super type identifier is redeclared', () => { @@ -110,14 +123,14 @@ describe('ClassDeclaration', () => { let asset = introspectUtils.loadLastDeclaration('test/data/parser/classdeclaration.dupeconceptname.cto', ConceptDeclaration); (() => { asset.validate(); - }).should.throw(/Duplicate class/); + }).should.throw(/Duplicate declaration/); }); it('should throw when enum name is duplicted in a modelfile', () => { let asset = introspectUtils.loadLastDeclaration('test/data/parser/classdeclaration.dupeenumname.cto', EnumDeclaration); (() => { asset.validate(); - }).should.throw(/Duplicate class/); + }).should.throw(/Duplicate declaration/); }); it('should throw when not abstract, not enum and not concept without an identifier', () => { @@ -147,6 +160,7 @@ describe('ClassDeclaration', () => { it('should call the visitor', () => { let clz = new ClassDeclaration(modelFile, { + $class: `${MetaModelNamespace}.ConceptDeclaration`, name: 'suchName', properties: [ ] @@ -165,12 +179,13 @@ describe('ClassDeclaration', () => { it('should return the class name', () => { let clz = new ClassDeclaration(modelFile, { + $class: `${MetaModelNamespace}.ConceptDeclaration`, name: 'suchName', properties: [ ] }); clz.getName().should.equal('suchName'); - clz.toString().should.equal('ClassDeclaration {id=com.hyperledger.testing@1.0.0.suchName super=Concept enum=false abstract=false}'); + clz.toString().should.equal('ClassDeclaration {id=com.hyperledger.testing@1.0.0.suchName super=Concept declarationKind=ConceptDeclaration abstract=false idField=null}'); }); }); @@ -254,6 +269,7 @@ describe('ClassDeclaration', () => { it('should return the fully qualified name if function is in a namespace', () => { let clz = new ClassDeclaration(modelFile, { + $class: `${MetaModelNamespace}.ConceptDeclaration`, name: 'suchName', properties: [ ] @@ -297,7 +313,7 @@ describe('ClassDeclaration', () => { it('toString',()=>{ const baseclass = modelManager.getType('com.testing.parent@1.0.0.Base'); - baseclass.toString().should.equal('ClassDeclaration {id=com.testing.parent@1.0.0.Base super=Participant enum=false abstract=true}'); + baseclass.toString().should.equal('ClassDeclaration {id=com.testing.parent@1.0.0.Base super=Participant declarationKind=ParticipantDeclaration abstract=true idField=id}'); }); }); diff --git a/packages/concerto-core/test/introspect/declaration.js b/packages/concerto-core/test/introspect/declaration.js index 2f184b029e..d1eedebcaf 100644 --- a/packages/concerto-core/test/introspect/declaration.js +++ b/packages/concerto-core/test/introspect/declaration.js @@ -18,62 +18,28 @@ const Declaration = require('../../src/introspect/declaration'); const ModelFile = require('../../src/introspect/modelfile'); require('chai').should(); -const should = require('chai').should(); const sinon = require('sinon'); describe('Declaration', () => { let modelFile; - let declaration; beforeEach(() => { modelFile = sinon.createStubInstance(ModelFile); - declaration = new Declaration(modelFile, { ast: true }); }); describe('#constructor', () => { - it('should throw if ast not specified', () => { + it('should throw if model file not specified', () => { (() => { new Declaration(null); - }).should.throw(/ast not specified/); - }); - - }); - - describe('#isIdentified', () => { - it('should be false', () => { - declaration.isIdentified().should.equal(false); - }); - }); - - describe('#isMapDeclaration', () => { - it('should be false', () => { - declaration.isMapDeclaration().should.equal(false); - }); - }); - - describe('#isSystemIdentified', () => { - it('should be false', () => { - declaration.isSystemIdentified().should.equal(false); - }); - }); - - describe('#getIdentifierFieldName', () => { - it('should be null', () => { - should.equal(declaration.getIdentifierFieldName(), null); + }).should.throw(/ModelFile not specified/); }); - }); - describe('#getType', () => { - it('should be null', () => { - should.equal(declaration.getType(), null); - }); - }); - - describe('#toString', () => { - it('should be null', () => { - should.equal(declaration.toString(), null); + it('should throw if ast not specified', () => { + (() => { + new Declaration(modelFile, null); + }).should.throw(/ast not specified/); }); }); }); diff --git a/packages/concerto-core/test/introspect/decorated.js b/packages/concerto-core/test/introspect/decorated.js index 55e803379a..6a3a8a5ccd 100644 --- a/packages/concerto-core/test/introspect/decorated.js +++ b/packages/concerto-core/test/introspect/decorated.js @@ -23,31 +23,23 @@ const sinon = require('sinon'); describe('Decorated', () => { let modelFile; - let decorated; beforeEach(() => { modelFile = sinon.createStubInstance(ModelFile); - decorated = new Decorated(modelFile, { ast: true }); }); describe('#constructor', () => { - it('should throw if ast not specified', () => { + it('should throw if ModelFile not specified', () => { (() => { new Decorated(null); - }).should.throw(/ast not specified/); + }).should.throw(/ModelFile not specified/); }); - }); - - describe('#getModelFile', () => { - - it('should throw as abstract', () => { + it('should throw if ast not specified', () => { (() => { - decorated.getModelFile(); - }).should.throw(/not implemented/); + new Decorated(modelFile, null); + }).should.throw(/ast not specified/); }); - }); - }); diff --git a/packages/concerto-core/test/introspect/decorator.js b/packages/concerto-core/test/introspect/decorator.js index 402a7985fe..ff60ed28ec 100644 --- a/packages/concerto-core/test/introspect/decorator.js +++ b/packages/concerto-core/test/introspect/decorator.js @@ -16,6 +16,7 @@ const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); +const ModelFile = require('../../src/introspect/modelfile'); const AssetDeclaration = require('../../src/introspect/assetdeclaration'); const Decorator = require('../../src/introspect/decorator'); @@ -25,6 +26,7 @@ const sinon = require('sinon'); describe('Decorator', () => { let mockAssetDeclaration; + let mockModelFile; const ast = { $class: `${MetaModelNamespace}.Decorator`, @@ -47,13 +49,14 @@ describe('Decorator', () => { beforeEach(() => { mockAssetDeclaration = sinon.createStubInstance(AssetDeclaration); + mockModelFile = sinon.createStubInstance(ModelFile); }); describe('#constructor', () => { it('should store values', () => { - const d = new Decorator(mockAssetDeclaration, ast); + const d = new Decorator(mockModelFile, mockAssetDeclaration, ast); d.getParent().should.equal(mockAssetDeclaration); d.getName().should.equal('Test'); d.getArguments().should.deep.equal(['one','two','three']); @@ -65,7 +68,7 @@ describe('Decorator', () => { describe('#accept', () => { it('should call the visitor', () => { - const d = new Decorator(mockAssetDeclaration, ast); + const d = new Decorator(mockModelFile, mockAssetDeclaration, ast); let visitor = { visit: sinon.stub() }; diff --git a/packages/concerto-core/test/introspect/decorators.js b/packages/concerto-core/test/introspect/decorators.js index 58f7735958..a1212990c0 100644 --- a/packages/concerto-core/test/introspect/decorators.js +++ b/packages/concerto-core/test/introspect/decorators.js @@ -190,23 +190,25 @@ describe('Decorators', () => { * Process the decorator, and return a specific implementation class for that * decorator, or return null if this decorator is not handled by this processor. * @abstract + * @param {ModelFile} modelFile - the modelFile for the decorator * @param {ClassDeclaration | Property} parent - the owner of this property * @param {Object} ast - The AST created by the parser * @return {Decorator} The decorator. */ - newDecorator(parent, ast) { + newDecorator(modelFile, parent, ast) { if (ast.name !== 'bar') { return null; } return new(class MyDecorator extends Decorator { /** * Create a Decorator. + * @param {ModelFile} modelFile - the modelFile for the decorator * @param {ClassDeclaration | Property} parent - the owner of this property * @param {Object} ast - The AST created by the parser * @throws {IllegalModelException} */ - constructor(parent, ast) { - super(parent, ast); + constructor(modelFile, parent, ast) { + super(modelFile, parent, ast); } /** * My method. @@ -215,7 +217,7 @@ describe('Decorators', () => { myMethod() { return 'woop'; } - })(parent, ast); + })(modelFile, parent, ast); } }); diff --git a/packages/concerto-core/test/introspect/identifieddeclaration.js b/packages/concerto-core/test/introspect/identifieddeclaration.js deleted file mode 100644 index cdf1dd5915..0000000000 --- a/packages/concerto-core/test/introspect/identifieddeclaration.js +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -'use strict'; - -const ModelManager = require('../../src/modelmanager'); -const IdentifiedDeclaration = require('../../src/introspect/identifieddeclaration'); - -require('chai').should(); - -describe('IdentifiedDeclaration', () => { - - beforeEach(() => { - }); - - afterEach(() => { - }); - - describe('#hasInstance', () => { - - it('should identify instance', () => { - const mm = new ModelManager(); - mm.addCTOModel( ` -namespace test@1.0.0 - -asset Order { - o Double price -} - `, 'test.cto'); - - const order = mm.getType('test@1.0.0.Order'); - order.should.not.be.null; - (order instanceof IdentifiedDeclaration).should.be.true; - }); - }); - - describe('#identified', () => { - - it('should create a system identifier', () => { - const mm = new ModelManager(); - mm.addCTOModel( ` -namespace test@1.0.0 - -asset Order { - o Double price -} - `, 'test.cto'); - - const order = mm.getType('test@1.0.0.Order'); - order.should.not.be.null; - order.getProperties().length.should.equal(2); - order.getIdentifierFieldName().should.equal('$identifier'); - }); - - it('should allow declaring system identifier', () => { - const mm = new ModelManager(); - mm.addCTOModel( ` -namespace test@1.0.0 - -asset Order { - o Double price -} - `, 'test.cto'); - - const order = mm.getType('test@1.0.0.Order'); - order.should.not.be.null; - order.getProperties().length.should.equal(2); - order.getIdentifierFieldName().should.equal('$identifier'); - }); - - it('should allow declaring explicit identifier', () => { - const mm = new ModelManager(); - mm.addCTOModel( ` -namespace test@1.0.0 - -asset Order identified by sku { - o String sku - o Double price -} - `, 'test.cto'); - - const order = mm.getType('test@1.0.0.Order'); - order.should.not.be.null; - order.getProperties().length.should.equal(3); // XXX Assets always have an identifier - order.getIdentifierFieldName().should.equal('sku'); - order.isSystemIdentified().should.be.false; - order.isExplicitlyIdentified().should.be.true; - }); - - it('should not allow overriding explicit identifier', () => { - const mm = new ModelManager(); - - (() => { - mm.addCTOModel( ` - namespace test@1.0.0 - - asset Order identified by sku { - o String sku - o Double price - } - - asset FancyOrder identified extends Order { - } - - `, 'test.cto'); - }).should.throw(/Super class test@1.0.0.Order has an explicit identifier sku that cannot be redeclared./); - }); - - it('should not allow overriding system identifier', () => { - const mm = new ModelManager(); - - (() => { - mm.addCTOModel( ` - namespace test@1.0.0 - - asset FancyOrder identified { - o String sku - } - - `, 'test.cto'); - }).should.throw(/Class "FancyOrder" has more than one field named "\$identifier". File 'test.cto': line 4 column 17, to line 6 column 18. /); - }); - - it('should not allow overriding explicit identifier with a system identifier', () => { - const mm = new ModelManager(); - - (() => { - mm.addCTOModel( ` - namespace test@1.0.0 - - asset Order identified by sku { - o Double price - o String sku - } - - asset FancyOrder identified extends Order { - o String model - } - - `, 'test.cto'); - }).should.throw(/Super class test@1.0.0.Order has an explicit identifier sku that cannot be redeclared./); - }); - - it('should not allow overriding explicit identifier with an explicit identifier', () => { - const mm = new ModelManager(); - - (() => { - mm.addCTOModel( ` - namespace test@1.0.0 - - asset Order identified by sku { - o Double price - o String sku - } - - asset FancyOrder identified by model extends Order { - o String model - } - - `, 'test.cto'); - }).should.throw(/Super class test@1.0.0.Order has an explicit identifier sku that cannot be redeclared./); - }); - - it('should not allow field called $identifier', () => { - const mm = new ModelManager(); - - (() => { - mm.addCTOModel( ` - namespace test@1.0.0 - - asset Order { - o String $identifier - }`, 'test.cto'); - }).should.throw(/Invalid field name '\$identifier'/); - }); - - it('should allow field called $foo', () => { - const mm = new ModelManager(); - - mm.addCTOModel( ` - namespace test@1.0.0 - - asset Order { - o String $foo - }`, 'test.cto'); - - const order = mm.getType('test@1.0.0.Order'); - order.should.not.be.null; - order.getProperties().length.should.equal(2); // XXX Assets always have an identifier - }); - - it('should allow abstract assets without an identifier', () => { - const mm = new ModelManager(); - mm.addCTOModel( ` - namespace test@1.0.0 - - abstract asset Order { - o Double price - } - - asset FancyOrder identified by sku extends Order { - o String sku - } - `, 'test.cto'); - - const order = mm.getType('test@1.0.0.Order'); - order.should.not.be.null; - order.getProperties().length.should.equal(2); // XXX Assets always have an identifier - order.isSystemIdentified().should.be.true; - order.isExplicitlyIdentified().should.be.false; - }); - - }); -}); diff --git a/packages/concerto-core/test/introspect/introspector.js b/packages/concerto-core/test/introspect/introspector.js index 716c6e0d14..7509b2239c 100644 --- a/packages/concerto-core/test/introspect/introspector.js +++ b/packages/concerto-core/test/introspect/introspector.js @@ -56,8 +56,8 @@ describe('Introspector', () => { modelManager.addCTOModel(modelBase, 'model-base.cto'); const introspector = new Introspector(modelManager); let classDecl = introspector.getClassDeclarations(); - const scalarDecl = classDecl.filter(declaration => declaration.isScalarDeclaration?.()); - const mapDecl = classDecl.filter(declaration => declaration.isMapDeclaration?.()); + const scalarDecl = classDecl.filter(declaration => declaration.isScalar()); + const mapDecl = classDecl.filter(declaration => declaration.isMap()); classDecl.length.should.equal(40); scalarDecl.length.should.equal(0); mapDecl.length.should.equal(0); diff --git a/packages/concerto-core/test/introspect/mapdeclaration.js b/packages/concerto-core/test/introspect/mapdeclaration.js index 5eb78d241b..1d0ee8b0ab 100644 --- a/packages/concerto-core/test/introspect/mapdeclaration.js +++ b/packages/concerto-core/test/introspect/mapdeclaration.js @@ -108,39 +108,6 @@ describe('MapDeclaration', () => { let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.primitive.datetime.cto', MapDeclaration); decl.validate(); }); - - it('should throw if invalid $class provided for Map Key', () => { - (() => - { - new MapDeclaration(modelFile, { - $class: 'concerto.metamodel@1.0.0.MapDeclaration', - name: 'MapPermutation1', - key: { - $class: 'concerto.metamodel@1.0.0.BadMapKeyType' - }, - value: { - $class: 'concerto.metamodel@1.0.0.StringMapValueType' - } - }); - }).should.throw(IllegalModelException); - }); - - - it('should throw if invalid $class provided for Map Value', () => { - (() => - { - new MapDeclaration(modelFile, { - $class: 'concerto.metamodel@1.0.0.MapDeclaration', - name: 'MapPermutation1', - key: { - $class: 'concerto.metamodel@1.0.0.StringMapKeyType' - }, - value: { - $class: 'concerto.metamodel@1.0.0.BadMapValueType' - } - }); - }).should.throw(IllegalModelException); - }); }); describe('#validate success scenarios - Map Key', () => { @@ -280,21 +247,6 @@ describe('MapDeclaration', () => { describe('#validate failure scenarios - Map Key', () => { - it('should throw if ast contains illegal Map Key Type', () => { - (() => { - new MapDeclaration(modelFile, { - $class: 'concerto.metamodel@1.0.0.MapDeclaration', - name: 'MapPermutation1', - key: { - $class: 'concerto.metamodel@1.0.0.UnSupportedMapKeyType' - }, - value: { - $class: 'concerto.metamodel@1.0.0.StringMapValueType' - } - }); - }).should.throw(IllegalModelException); - }); - it('should throw if ast contains illegal Map Key Type - Concept', () => { (() => { let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.declaration.concept.cto', MapDeclaration); @@ -330,85 +282,6 @@ describe('MapDeclaration', () => { }); }); - it('should throw if ast contains illegal Map Key Type - Boolean', () => { - (() => { - new MapDeclaration(modelFile, { - $class: 'concerto.metamodel@1.0.0.MapDeclaration', - name: 'MapPermutation1', - key: { - $class: 'concerto.metamodel@1.0.0.BooleanMapKeyType' - }, - value: { - $class: 'concerto.metamodel@1.0.0.StringMapValueType' - } - }); - }).should.throw(IllegalModelException); - }); - - it('should throw if ast contains illegal Map Key Type - Integer', () => { - (() => { - new MapDeclaration(modelFile, { - $class: 'concerto.metamodel@1.0.0.MapDeclaration', - name: 'MapPermutation1', - key: { - $class: 'concerto.metamodel@1.0.0.IntegerMapKeyType' - }, - value: { - $class: 'concerto.metamodel@1.0.0.StringMapValueType' - } - }); - }).should.throw(IllegalModelException); - }); - - it('should throw if ast contains illegal Map Key Type - Long', () => { - (() => { - new MapDeclaration(modelFile, { - $class: 'concerto.metamodel@1.0.0.MapDeclaration', - name: 'MapPermutation1', - key: { - $class: 'concerto.metamodel@1.0.0.LongMapKeyType' - }, - value: { - $class: 'concerto.metamodel@1.0.0.StringMapValueType' - } - }); - }).should.throw(IllegalModelException); - }); - - it('should throw if ast contains illegal Map Key Type - Double', () => { - (() => { - new MapDeclaration(modelFile, { - $class: 'concerto.metamodel@1.0.0.MapDeclaration', - name: 'MapPermutation1', - key: { - $class: 'concerto.metamodel@1.0.0.DoubleMapKeyType' - }, - value: { - $class: 'concerto.metamodel@1.0.0.StringMapValueType' - } - }); - }).should.throw(IllegalModelException); - }); - - it('should throw if ast contains illegal Map Key Type - Enum', () => { - (() => { - new MapDeclaration(modelFile, { - $class: 'concerto.metamodel@1.0.0.MapDeclaration', - name: 'MapPermutation1', - key: { - $class: 'concerto.metamodel@1.0.0.EnumMapKeyType', - type: { - $class: 'concerto.metamodel@1.0.0.TypeIdentifier', - name: 'States' - } - }, - value: { - $class: 'concerto.metamodel@1.0.0.StringMapValueType' - } - }); - }).should.throw(IllegalModelException); - }); - it('should throw when map key is imported and is an illegal Map Key Type', () => { (() => { const base_cto = fs.readFileSync('test/data/parser/mapdeclaration/base.cto', 'utf-8'); @@ -419,84 +292,6 @@ describe('MapDeclaration', () => { }); }); - describe('#validate failure scenarios - Map Value', () => { - - it('should throw if ObjectMapValueType does not contain type property ', () => { - (() => { - new MapDeclaration(modelFile, { - $class: 'concerto.metamodel@1.0.0.MapDeclaration', - name: 'MapPermutation1', - key: { - $class: 'concerto.metamodel@1.0.0.StringMapKeyType' - }, - value: { - $class: 'concerto.metamodel@1.0.0.ObjectMapValueType', - } - }); - }).should.throw(IllegalModelException); - }); - - it('should throw if ObjectMapValueType TypeIdentifier does not contain name property', () => { - (() => { - new MapDeclaration(modelFile, { - $class: 'concerto.metamodel@1.0.0.MapDeclaration', - name: 'MapPermutation1', - key: { - $class: 'concerto.metamodel@1.0.0.StringMapKeyType' - }, - value: { - $class: 'concerto.metamodel@1.0.0.ObjectMapValueType', - type: { - $class: 'concerto.metamodel@1.0.0.TypeIdentifier', - } - } - }); - }).should.throw(IllegalModelException); - }); - - it('should throw if ObjectMapValueType TypeIdentifier has bad $class', () => { - (() => { - new MapDeclaration(modelFile, { - $class: 'concerto.metamodel@1.0.0.MapDeclaration', - name: 'MapPermutation1', - key: { - $class: 'concerto.metamodel@1.0.0.StringMapKeyType' - }, - value: { - $class: 'concerto.metamodel@1.0.0.ObjectMapValueType', - type: { - $class: 'concerto.metamodel@1.0.0.BAD_$CLASS', - name: 'Person', - } - } - }); - }).should.throw(IllegalModelException); - }); - - it('should throw if ast contains illegal Map Value Property', () => { - (() => { - new MapDeclaration(modelFile, { - $class: 'concerto.metamodel@1.0.0.MapDeclaration', - name: 'MapPermutation1', - key: { - $class: 'concerto.metamodel@1.0.0.StringMapValueType' - }, - value: { - $class: 'concerto.metamodel@1.0.0.EnumMapValueType' - } - }); - }).should.throw(IllegalModelException); - }); - - - it('should throw when map value is a map declaration', function() { - (() => { - let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badvalue.declaration.map.cto', MapDeclaration); - decl.validate(); - }).should.throw(/MapDeclaration as Map Type Value is not supported:/); - }); - }); - describe('#accept', () => { it('should call the visitor', () => { let clz = new MapDeclaration(modelFile, { @@ -532,7 +327,7 @@ describe('MapDeclaration', () => { } }); (clz.getKey() instanceof MapKeyType).should.be.equal(true); - clz.getKey().ast.$class.should.equal('concerto.metamodel@1.0.0.StringMapKeyType'); + clz.getKey().getMetaType().should.equal('concerto.metamodel@1.0.0.StringMapKeyType'); }); it('should return the correct Type when called - String', () => { @@ -546,7 +341,7 @@ describe('MapDeclaration', () => { $class: 'concerto.metamodel@1.0.0.StringMapValueType' } }); - clz.getKey().getType().should.equal('String'); + clz.getKey().getMapKeyType().should.equal('String'); }); it('should return the correct Type when called - DateTime', () => { @@ -560,18 +355,18 @@ describe('MapDeclaration', () => { $class: 'concerto.metamodel@1.0.0.StringMapValueType' } }); - clz.getKey().getType().should.equal('DateTime'); + clz.getKey().getMapKeyType().should.equal('DateTime'); }); it('should return the correct Type when called - Scalar DateTime', () => { let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.scalar.datetime.cto', MapDeclaration); - decl.getKey().getType().should.equal('DATE'); + decl.getKey().getMapKeyType().should.equal('DATE'); }); it('should return the correct Type when called - Scalar String', () => { let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.scalar.string.cto', MapDeclaration); - decl.getKey().getType().should.equal('GUID'); + decl.getKey().getMapKeyType().should.equal('GUID'); }); it('should return the correct boolean value introspecting isValue or isKey', () => { @@ -585,8 +380,8 @@ describe('MapDeclaration', () => { $class: 'concerto.metamodel@1.0.0.DoubleMapValueType' } }); - expect(clz.getKey().isKey()).to.be.true; - expect(clz.getKey().isValue()).to.be.false; + expect(clz.getKey().isMapKey()).to.be.true; + expect(clz.getKey().isMapValue()).to.be.false; }); }); @@ -603,7 +398,7 @@ describe('MapDeclaration', () => { } }); (clz.getValue() instanceof MapValueType).should.be.equal(true); - clz.getValue().ast.$class.should.equal('concerto.metamodel@1.0.0.StringMapValueType'); + clz.getValue().getMetaType().should.equal('concerto.metamodel@1.0.0.StringMapValueType'); }); it('should return the correct Type when called - Boolean', () => { @@ -617,7 +412,7 @@ describe('MapDeclaration', () => { $class: 'concerto.metamodel@1.0.0.BooleanMapValueType' } }); - clz.getValue().getType().should.equal('Boolean'); + clz.getValue().getMapValueType().should.equal('Boolean'); }); it('should return the correct Type when called - DateTime', () => { @@ -631,7 +426,7 @@ describe('MapDeclaration', () => { $class: 'concerto.metamodel@1.0.0.DateTimeMapValueType' } }); - clz.getValue().getType().should.equal('DateTime'); + clz.getValue().getMapValueType().should.equal('DateTime'); }); it('should return the correct Type when called - String', () => { @@ -645,7 +440,7 @@ describe('MapDeclaration', () => { $class: 'concerto.metamodel@1.0.0.StringMapValueType' } }); - clz.getValue().getType().should.equal('String'); + clz.getValue().getMapValueType().should.equal('String'); }); it('should return the correct Type when called - Integer', () => { @@ -659,7 +454,7 @@ describe('MapDeclaration', () => { $class: 'concerto.metamodel@1.0.0.IntegerMapValueType' } }); - clz.getValue().getType().should.equal('Integer'); + clz.getValue().getMapValueType().should.equal('Integer'); }); it('should return the correct Type when called - Long', () => { @@ -673,7 +468,7 @@ describe('MapDeclaration', () => { $class: 'concerto.metamodel@1.0.0.LongMapValueType' } }); - clz.getValue().getType().should.equal('Long'); + clz.getValue().getMapValueType().should.equal('Long'); }); it('should return the correct Type when called - Double', () => { @@ -687,17 +482,17 @@ describe('MapDeclaration', () => { $class: 'concerto.metamodel@1.0.0.DoubleMapValueType' } }); - clz.getValue().getType().should.equal('Double'); + clz.getValue().getMapValueType().should.equal('Double'); }); it('should return the correct values when called - Scalar DateTime', () => { let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.scalar.datetime.cto', MapDeclaration); - decl.getValue().getType().should.equal('DATE'); + decl.getValue().getMapValueType().should.equal('DATE'); }); it('should return the correct values when called - Scalar String', () => { let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.scalar.string.cto', MapDeclaration); - decl.getValue().getType().should.equal('GUID'); + decl.getValue().getMapValueType().should.equal('GUID'); }); it('should return the correct boolean value introspecting isValue or isKey', () => { @@ -711,8 +506,8 @@ describe('MapDeclaration', () => { $class: 'concerto.metamodel@1.0.0.DoubleMapValueType' } }); - expect(clz.getValue().isValue()).to.be.true; - expect(clz.getValue().isKey()).to.be.false; + expect(clz.getValue().isMapValue()).to.be.true; + expect(clz.getValue().isMapKey()).to.be.false; }); }); @@ -737,16 +532,7 @@ describe('MapDeclaration', () => { let declaration = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.primitive.string.cto', MapDeclaration); declaration.declarationKind().should.equal('MapDeclaration'); declaration.getFullyQualifiedName().should.equal('com.acme@1.0.0.Dictionary'); - declaration.isMapDeclaration().should.equal(true); - }); - }); - - describe('#toString', () => { - it('should give the correct value for Map Declaration', () => { - let declaration = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.primitive.string.cto', MapDeclaration); - declaration.toString().should.equal('MapDeclaration {id=com.acme@1.0.0.Dictionary}'); - declaration.getKey().toString().should.equal('MapKeyType {id=String}'); - declaration.getValue().toString().should.equal('MapValueType {id=String}'); + declaration.isMap().should.equal(true); }); }); diff --git a/packages/concerto-core/test/introspect/modelfile.js b/packages/concerto-core/test/introspect/modelfile.js index 52fd777689..469af7d5d9 100644 --- a/packages/concerto-core/test/introspect/modelfile.js +++ b/packages/concerto-core/test/introspect/modelfile.js @@ -60,7 +60,7 @@ describe('ModelFile', () => { it('should throw when null ast provided', () => { (() => { new ModelFile(modelManager, null); - }).should.throw(/ast not specified/); + }).should.throw(/ModelFile expects a Concerto model AST as input./); }); it('should throw when non object ast provided', () => { diff --git a/packages/concerto-core/test/introspect/property.js b/packages/concerto-core/test/introspect/property.js index 53e3ad0e5b..14fbc72de0 100644 --- a/packages/concerto-core/test/introspect/property.js +++ b/packages/concerto-core/test/introspect/property.js @@ -42,7 +42,7 @@ describe('Property', () => { $class: `${MetaModelNamespace}.StringProperty`, name: null }); - }).should.throw(/No name for type/); + }).should.throw(/Property of type String must have a name/); }); it('should not throw for an identifier named null', () => { @@ -62,7 +62,7 @@ describe('Property', () => { name: 'suchType', } }); - p.type.should.equal('suchType'); + p.getPropertyType().should.equal('suchType'); }); it('should handle a missing incoming property type', () => { @@ -70,7 +70,7 @@ describe('Property', () => { $class: `${MetaModelNamespace}.ObjectProperty`, name: 'property', }); - should.equal(p.type, null); + should.equal(p.getPropertyType(), null); }); it('should not be an array by default', () => { @@ -96,7 +96,7 @@ describe('Property', () => { $class: `${MetaModelNamespace}.StringProperty`, name: '1st', }); - }).should.throw(/Invalid property name '1st'/); + }).should.throw(/Invalid model element name '1st'/); }); }); diff --git a/packages/concerto-core/test/introspect/property_ex.js b/packages/concerto-core/test/introspect/property_ex.js index 071b29a1a9..9f24ec838d 100644 --- a/packages/concerto-core/test/introspect/property_ex.js +++ b/packages/concerto-core/test/introspect/property_ex.js @@ -83,7 +83,7 @@ describe('Property', function () { it('toString works', function () { const person = modelManager.getType('org.acme.l1@1.0.0.Car'); const field = person.getProperty('owner'); - field.toString().should.equal('RelationshipDeclaration {name=owner, type=org.acme.l1@1.0.0.Person, array=false, optional=false}'); + field.toString().should.equal('RelationshipProperty {name=owner, type=org.acme.l1@1.0.0.Person, array=false, optional=false}'); }); }); }); diff --git a/packages/concerto-core/test/introspect/relationshipdeclaration.js b/packages/concerto-core/test/introspect/relationshipproperty.js similarity index 89% rename from packages/concerto-core/test/introspect/relationshipdeclaration.js rename to packages/concerto-core/test/introspect/relationshipproperty.js index cc25105bc1..6dbcceab33 100644 --- a/packages/concerto-core/test/introspect/relationshipdeclaration.js +++ b/packages/concerto-core/test/introspect/relationshipproperty.js @@ -17,14 +17,14 @@ const ModelManager = require('../../src/modelmanager'); const sinon = require('sinon'); const ClassDeclaration = require('../../src/introspect/classdeclaration'); -const RelationshipDeclaration = require('../../src/introspect/relationshipdeclaration'); +const RelationshipProperty = require('../../src/introspect/relationshipproperty'); const Util = require('../composer/composermodelutility'); const chai = require('chai'); chai.should(); chai.use(require('chai-things')); -describe('RelationshipDeclaration', function () { +describe('RelationshipProperty', function () { let modelManager; let mockClassDeclaration; @@ -52,9 +52,9 @@ describe('RelationshipDeclaration', function () { modelManager.addCTOModel(levelOneModel); const vehicleDeclaration = modelManager.getType('org.acme.l1@1.0.0.Car'); const field = vehicleDeclaration.getProperty('owner'); - (field instanceof RelationshipDeclaration).should.be.true; + (field instanceof RelationshipProperty).should.be.true; // stub the getType method to return null - sinon.stub(field, 'getType').callsFake(function(){return null;}); + sinon.stub(field, 'getPropertyType').callsFake(function(){return null;}); (function () { field.validate(vehicleDeclaration); }).should.throw(/Relationship must have a type/); @@ -81,7 +81,7 @@ describe('RelationshipDeclaration', function () { modelManager.addCTOModel(model2); const vehicleDeclaration = modelManager.getType('org.acme.l2@1.0.0.Car'); const field = vehicleDeclaration.getProperty('owner'); - (field instanceof RelationshipDeclaration).should.be.true; + (field instanceof RelationshipProperty).should.be.true; modelManager.getType = () => { return null; }; (function () { @@ -104,7 +104,7 @@ describe('RelationshipDeclaration', function () { modelManager.addCTOModel(model); const vehicleDeclaration = modelManager.getType('org.acme.l1@1.0.0.Car'); const field = vehicleDeclaration.getProperty('owner'); - (field instanceof RelationshipDeclaration).should.be.true; + (field instanceof RelationshipProperty).should.be.true; field.getParent().getModelFile().getType = () => {return mockClassDeclaration;}; (function () { diff --git a/packages/concerto-core/test/introspect/scalardeclaration.js b/packages/concerto-core/test/introspect/scalardeclaration.js index a653f39612..cf7e38dd95 100644 --- a/packages/concerto-core/test/introspect/scalardeclaration.js +++ b/packages/concerto-core/test/introspect/scalardeclaration.js @@ -14,6 +14,8 @@ 'use strict'; +const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); + const ScalarDeclaration = require('../../src/introspect/scalardeclaration'); const IntrospectUtils = require('./introspectutils'); const ParserUtil = require('./parserutility'); @@ -42,13 +44,14 @@ describe('ScalarDeclaration', () => { let asset = introspectUtils.loadLastDeclaration('test/data/parser/scalardeclaration.dupeboolean.cto', ScalarDeclaration); (() => { asset.validate(); - }).should.throw(/Duplicate class/); + }).should.throw(/Duplicate declaration/); }); }); describe('#accept', () => { it('should call the visitor', () => { let clz = new ScalarDeclaration(modelFile, { + $class: `${MetaModelNamespace}.StringScalar`, name: 'suchName' }); let visitor = { @@ -64,10 +67,11 @@ describe('ScalarDeclaration', () => { describe('#getName', () => { it('should return the scalar name', () => { let clz = new ScalarDeclaration(modelFile, { - name: 'suchName' + name: 'suchName', + $class: `${MetaModelNamespace}.BooleanScalar` }); clz.getName().should.equal('suchName'); - clz.toString().should.equal('ScalarDeclaration {id=com.hyperledger.testing@1.0.0.suchName}'); + clz.toString().should.equal('ScalarDeclaration {id=com.hyperledger.testing@1.0.0.suchName scalarType=Boolean}'); }); }); @@ -89,38 +93,13 @@ describe('ScalarDeclaration', () => { it('should return the fully qualified name if function is in a namespace', () => { let clz = new ScalarDeclaration(modelFile, { name: 'suchName', + $class: `${MetaModelNamespace}.StringScalar`, }); clz.getFullyQualifiedName().should.equal('com.hyperledger.testing@1.0.0.suchName'); }); }); - describe('#getSuperType', () => { - const modelFileName = 'test/data/parser/scalardeclaration.ssn.cto'; - - beforeEach(() => { - const modelFiles = introspectUtils.loadModelFiles([modelFileName], modelManager); - modelManager.addModelFiles(modelFiles); - }); - it('should return null', () => { - const testClass = modelManager.getType('com.testing@1.0.0.SSN'); - should.equal(testClass.getSuperType(), null); - }); - }); - - describe('#getSuperTypeDeclaration', () => { - const modelFileName = 'test/data/parser/scalardeclaration.ssn.cto'; - - beforeEach(() => { - const modelFiles = introspectUtils.loadModelFiles([modelFileName], modelManager); - modelManager.addModelFiles(modelFiles); - }); - it('should return null', () => { - const testClass = modelManager.getType('com.testing@1.0.0.SSN'); - should.equal(testClass.getSuperTypeDeclaration(), null); - }); - }); - describe('#getValidator', () => { const modelFileName = 'test/data/parser/scalardeclaration.ssn.cto'; @@ -150,19 +129,6 @@ describe('ScalarDeclaration', () => { }); }); - describe('#isIdentified', () => { - 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@1.0.0.SSN'); - testClass.isIdentified().should.be.false; - }); - }); - describe('#isAsset', () => { const modelFileName = 'test/data/parser/scalardeclaration.ssn.cto'; @@ -176,45 +142,6 @@ describe('ScalarDeclaration', () => { }); }); - describe('#isSystemIdentified', () => { - 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@1.0.0.SSN'); - testClass.isSystemIdentified().should.be.false; - }); - }); - - describe('#getIdentifierFieldName', () => { - const modelFileName = 'test/data/parser/scalardeclaration.ssn.cto'; - - beforeEach(() => { - const modelFiles = introspectUtils.loadModelFiles([modelFileName], modelManager); - modelManager.addModelFiles(modelFiles); - }); - it('should return null', () => { - const testClass = modelManager.getType('com.testing@1.0.0.SSN'); - should.equal(testClass.getIdentifierFieldName(), null); - }); - }); - - describe('#isAbstract', () => { - const modelFileName = 'test/data/parser/scalardeclaration.ssn.cto'; - - beforeEach(() => { - const modelFiles = introspectUtils.loadModelFiles([modelFileName], modelManager); - modelManager.addModelFiles(modelFiles); - }); - it('should return true', () => { - const testClass = modelManager.getType('com.testing@1.0.0.SSN'); - testClass.isAbstract().should.be.true; - }); - }); - describe('#isAsset', () => { const modelFileName = 'test/data/parser/scalardeclaration.ssn.cto'; diff --git a/packages/concerto-core/test/model/concept.js b/packages/concerto-core/test/model/concept.js index 6df2e41dcc..0ac5c8ce2f 100644 --- a/packages/concerto-core/test/model/concept.js +++ b/packages/concerto-core/test/model/concept.js @@ -42,7 +42,7 @@ describe('Concept', function () { let classDecl = null; before(function () { - modelManager = new ModelManager(); + modelManager = new ModelManager( {enableMapType: true} ); Util.addComposerModel(modelManager); }); @@ -140,7 +140,6 @@ describe('Concept', function () { invCount:10, invType:'NEWBATCH', dictionary: { - $class: 'org.acme.biznet@1.0.0.Dictionary', key1: 'value1', key2: 'value2', } diff --git a/packages/concerto-core/test/models/animaltracking.js b/packages/concerto-core/test/models/animaltracking.js index 0a8f445018..1b73aa6dd8 100644 --- a/packages/concerto-core/test/models/animaltracking.js +++ b/packages/concerto-core/test/models/animaltracking.js @@ -19,7 +19,7 @@ require('chai').should(); const Factory = require('../../src/factory'); const ModelManager = require('../../src/modelmanager'); const Relationship = require('../../src/model/relationship'); -const RelationshipDeclaration = require('../../src/introspect/relationshipdeclaration'); +const RelationshipProperty = require('../../src/introspect/relationshipproperty'); const Serializer = require('../../src/serializer'); const fs = require('fs'); const Util = require('../composer/composermodelutility'); @@ -77,7 +77,7 @@ describe('animaltracking Model', function(){ incomingAnimals.should.not.be.null; incomingAnimals.getType().should.equal('Animal'); incomingAnimals.isArray().should.be.true; - (incomingAnimals instanceof RelationshipDeclaration).should.be.true; + (incomingAnimals instanceof RelationshipProperty).should.be.true; }); it('create and serialize instance', function() { diff --git a/packages/concerto-core/test/models/farm2fork.js b/packages/concerto-core/test/models/farm2fork.js index 4b933e1567..0d2843aa38 100644 --- a/packages/concerto-core/test/models/farm2fork.js +++ b/packages/concerto-core/test/models/farm2fork.js @@ -16,7 +16,7 @@ require('chai').should(); const ModelManager = require('../../src/modelmanager'); -const RelationshipDeclaration = require('../../src/introspect/relationshipdeclaration'); +const RelationshipProperty = require('../../src/introspect/relationshipproperty'); const Util = require('../composer/composermodelutility'); const fs = require('fs'); @@ -67,7 +67,7 @@ describe('Farm2Fork Model', function() { let previousKeeperField = animal.getProperty('previousKeeper'); previousKeeperField.getName().should.equal('previousKeeper'); previousKeeperField.isArray().should.be.true; - (previousKeeperField instanceof RelationshipDeclaration).should.be.true; + (previousKeeperField instanceof RelationshipProperty).should.be.true; previousKeeperField.getType().should.equal('MyParticipant'); // test the VehicleTransferredToScrapMerchant class @@ -84,7 +84,7 @@ describe('Farm2Fork Model', function() { let animalField = txDecl.getProperty('animal'); animalField.should.not.be.null; animalField.getType().should.equal('Animal'); - (animalField instanceof RelationshipDeclaration).should.be.true; + (animalField instanceof RelationshipProperty).should.be.true; }); }); }); diff --git a/packages/concerto-core/test/models/test.js b/packages/concerto-core/test/models/test.js index 6bbb9bd20d..701eeb45c9 100644 --- a/packages/concerto-core/test/models/test.js +++ b/packages/concerto-core/test/models/test.js @@ -16,7 +16,7 @@ const Factory = require('../../src/factory'); const ModelManager = require('../../src/modelmanager'); -const RelationshipDeclaration = require('../../src/introspect/relationshipdeclaration'); +const RelationshipProperty = require('../../src/introspect/relationshipproperty'); const Serializer = require('../../src/serializer'); const TypeNotFoundException = require('../../src/typenotfoundexception'); const fs = require('fs'); @@ -294,7 +294,7 @@ describe('Test Model', function(){ // Nary relationship let previousOwnersField = vehicle.getProperty('previousOwners'); previousOwnersField.isArray().should.be.true; - (previousOwnersField instanceof RelationshipDeclaration).should.be.true; + (previousOwnersField instanceof RelationshipProperty).should.be.true; previousOwnersField.getType().should.equal('MyParticipant'); // test the VehicleTransferredToScrapMerchant class @@ -311,7 +311,7 @@ describe('Test Model', function(){ let vehicleField = txDecl.getProperty('vehicle'); vehicleField.should.not.be.null; vehicleField.getType().should.equal('Vehicle'); - (vehicleField instanceof RelationshipDeclaration).should.be.true; + (vehicleField instanceof RelationshipProperty).should.be.true; }); }); diff --git a/packages/concerto-core/test/serializer/instancegenerator.js b/packages/concerto-core/test/serializer/instancegenerator.js index fe5f3b2239..75bc9c5fa8 100644 --- a/packages/concerto-core/test/serializer/instancegenerator.js +++ b/packages/concerto-core/test/serializer/instancegenerator.js @@ -42,7 +42,7 @@ describe('InstanceGenerator', () => { }; beforeEach(() => { - modelManager = new ModelManager(); + modelManager = new ModelManager({enableMapType: true}); Util.addComposerModel(modelManager); factory = new Factory(modelManager); parameters = { @@ -67,7 +67,7 @@ describe('InstanceGenerator', () => { it('should throw on unrecognized thing', () => { (() => { visitor.visit(dayjs.utc(), {}); - }).should.throw(/Unrecognised/); + }).should.throw(/Model element is invalid/); }); it('should generate a default value for a string property', () => { @@ -111,7 +111,7 @@ describe('InstanceGenerator', () => { values[1].should.be.a('string'); }); - it('should generate a value with specified lentgh constraint for a string property', () => { + it('should generate a value with specified length constraint for a string property', () => { useSampleGenerator(); let resource = test(`namespace org.acme.test@1.0.0 asset MyAsset identified by assetId { diff --git a/packages/concerto-core/test/serializer/jsongenerator.js b/packages/concerto-core/test/serializer/jsongenerator.js index a74b8d7e7c..1f4fabaa98 100644 --- a/packages/concerto-core/test/serializer/jsongenerator.js +++ b/packages/concerto-core/test/serializer/jsongenerator.js @@ -121,7 +121,7 @@ describe('JSONGenerator', () => { it('should throw an error for an unrecognized type', () => { (() => { jsonGenerator.visit(3.142, {}); - }).should.throw(/Unrecognised/); + }).should.throw(/Model element is invalid/); }); }); diff --git a/packages/concerto-core/test/serializer/jsonpopulator.js b/packages/concerto-core/test/serializer/jsonpopulator.js index b875376327..ac131684db 100644 --- a/packages/concerto-core/test/serializer/jsonpopulator.js +++ b/packages/concerto-core/test/serializer/jsonpopulator.js @@ -95,7 +95,7 @@ describe('JSONPopulator', () => { it('should throw an error for an unrecognized type', () => { (() => { jsonPopulator.visit(3.142, {}); - }).should.throw(/Unrecognised/); + }).should.throw(/Model element is invalid/); }); }); diff --git a/packages/concerto-core/test/serializer/maptype/serializer.js b/packages/concerto-core/test/serializer/maptype/serializer.js index 44a5a5f7b5..9ed9f53a30 100644 --- a/packages/concerto-core/test/serializer/maptype/serializer.js +++ b/packages/concerto-core/test/serializer/maptype/serializer.js @@ -14,6 +14,8 @@ 'use strict'; +const dayjs = require('dayjs'); + const Factory = require('../../../src/factory'); const ModelManager = require('../../../src/modelmanager'); const Resource = require('../../../src/model/resource'); @@ -292,8 +294,8 @@ describe('Serializer', () => { let concept = factory.newConcept('org.acme.sample@1.0.0', 'Concepts'); concept.birthday = new Map(); - concept.birthday.set('Bob', '2023-10-28T01:02:03Z'); - concept.birthday.set('Alice', '2024-10-28T01:02:03Z'); + concept.birthday.set('Bob', dayjs('2023-10-28T01:02:03Z')); + concept.birthday.set('Alice', dayjs('2024-10-28T01:02:03Z')); // serialize and assert const json = serializer.toJSON(concept); @@ -301,8 +303,8 @@ describe('Serializer', () => { json.should.deep.equal({ $class: 'org.acme.sample@1.0.0.Concepts', birthday: { - Bob: '2023-10-28T01:02:03Z', - Alice: '2024-10-28T01:02:03Z' + Bob: '2023-10-28T01:02:03.000Z', + Alice: '2024-10-28T01:02:03.000Z' } }); @@ -311,8 +313,8 @@ describe('Serializer', () => { resource.should.be.an.instanceOf(Resource); resource.birthday.should.be.an.instanceOf(Map); - resource.birthday.get('Bob').should.equal('2023-10-28T01:02:03Z'); - resource.birthday.get('Alice').should.equal('2024-10-28T01:02:03Z'); + resource.birthday.get('Bob').unix().should.equal(dayjs('2023-10-28T01:02:03Z').unix()); + resource.birthday.get('Alice').unix().should.equal(dayjs('2024-10-28T01:02:03Z').unix()); }); it('should serialize -> deserialize with a Map ', () => { @@ -320,8 +322,8 @@ describe('Serializer', () => { let concept = factory.newConcept('org.acme.sample@1.0.0', 'Concepts'); concept.celebration = new Map(); - concept.celebration.set('BobBirthday', '2022-11-28T01:02:03Z'); - concept.celebration.set('AliceAnniversary', '2023-10-28T01:02:03Z'); + concept.celebration.set('BobBirthday', dayjs('2022-11-28T01:02:03Z')); + concept.celebration.set('AliceAnniversary', dayjs('2023-10-28T01:02:03Z')); // serialize and assert const json = serializer.toJSON(concept); @@ -329,8 +331,8 @@ describe('Serializer', () => { json.should.deep.equal({ $class: 'org.acme.sample@1.0.0.Concepts', celebration: { - 'BobBirthday': '2022-11-28T01:02:03Z', - 'AliceAnniversary': '2023-10-28T01:02:03Z', + 'BobBirthday': '2022-11-28T01:02:03.000Z', + 'AliceAnniversary': '2023-10-28T01:02:03.000Z', } }); @@ -339,8 +341,8 @@ describe('Serializer', () => { resource.should.be.an.instanceOf(Resource); resource.celebration.should.be.an.instanceOf(Map); - resource.celebration.get('BobBirthday').should.equal('2022-11-28T01:02:03Z'); - resource.celebration.get('AliceAnniversary').should.equal('2023-10-28T01:02:03Z'); + resource.celebration.get('BobBirthday').unix().should.equal(dayjs('2022-11-28T01:02:03Z').unix()); + resource.celebration.get('AliceAnniversary').unix().should.equal(dayjs('2023-10-28T01:02:03Z').unix()); }); it('should serialize -> deserialize with a Map ', () => { @@ -383,8 +385,8 @@ describe('Serializer', () => { let concept = factory.newConcept('org.acme.sample@1.0.0', 'Concepts'); concept.appointment = new Map(); - concept.appointment.set('2023-11-28T01:02:03Z', 'BobBirthday'); - concept.appointment.set('2024-10-28T01:02:03Z', 'AliceAnniversary'); + concept.appointment.set(dayjs('2023-11-28T01:02:03Z'), 'BobBirthday'); + concept.appointment.set(dayjs('2024-10-28T01:02:03Z'), 'AliceAnniversary'); // serialize and assert const json = serializer.toJSON(concept); @@ -392,8 +394,8 @@ describe('Serializer', () => { json.should.deep.equal({ $class: 'org.acme.sample@1.0.0.Concepts', appointment: { - '2023-11-28T01:02:03Z': 'BobBirthday', - '2024-10-28T01:02:03Z': 'AliceAnniversary' + '2023-11-28T01:02:03.000Z': 'BobBirthday', + '2024-10-28T01:02:03.000Z': 'AliceAnniversary' } }); @@ -402,8 +404,9 @@ describe('Serializer', () => { resource.should.be.an.instanceOf(Resource); resource.appointment.should.be.an.instanceOf(Map); - resource.appointment.get('2023-11-28T01:02:03Z').should.equal('BobBirthday'); - resource.appointment.get('2024-10-28T01:02:03Z').should.equal('AliceAnniversary'); + const keyIt = resource.appointment.keys(); + resource.appointment.get(keyIt.next().value).should.equal('BobBirthday'); + resource.appointment.get(keyIt.next().value).should.equal('AliceAnniversary'); }); it('should serialize -> deserialize with a Map : Scalar extends String', () => { @@ -439,8 +442,8 @@ describe('Serializer', () => { let concept = factory.newConcept('org.acme.sample@1.0.0', 'Concepts'); concept.stopwatch = new Map(); - concept.stopwatch.set('2023-10-28T00:00:00Z', '2023-10-28T11:12:13Z'); - concept.stopwatch.set('2024-11-28T00:00:00Z', '2024-11-28T11:12:13Z'); + concept.stopwatch.set(dayjs('2023-10-28T00:00:00Z'), dayjs('2023-10-28T11:12:13Z')); + concept.stopwatch.set(dayjs('2024-11-28T00:00:00Z'), dayjs('2024-11-28T11:12:13Z')); // serialize and assert const json = serializer.toJSON(concept); @@ -448,8 +451,8 @@ describe('Serializer', () => { json.should.deep.equal({ $class: 'org.acme.sample@1.0.0.Concepts', stopwatch: { - '2023-10-28T00:00:00Z': '2023-10-28T11:12:13Z', - '2024-11-28T00:00:00Z': '2024-11-28T11:12:13Z', + '2023-10-28T00:00:00.000Z': '2023-10-28T11:12:13.000Z', + '2024-11-28T00:00:00.000Z': '2024-11-28T11:12:13.000Z', } }); @@ -458,8 +461,9 @@ describe('Serializer', () => { resource.should.be.an.instanceOf(Resource); resource.stopwatch.should.be.an.instanceOf(Map); - resource.stopwatch.get('2023-10-28T00:00:00Z').should.equal('2023-10-28T11:12:13Z'); - resource.stopwatch.get('2024-11-28T00:00:00Z').should.equal('2024-11-28T11:12:13Z'); + const keyIt = resource.stopwatch.keys(); + resource.stopwatch.get(keyIt.next().value).unix().should.equal(dayjs('2023-10-28T11:12:13Z').unix()); + resource.stopwatch.get(keyIt.next().value).unix().should.equal(dayjs('2024-11-28T11:12:13Z').unix()); }); it('should serialize -> deserialize with a Map ', () => { @@ -503,8 +507,8 @@ describe('Serializer', () => { let concept = factory.newConcept('org.acme.sample@1.0.0', 'Concepts'); concept.diary = new Map(); - concept.diary.set('2023-10-28T01:02:03Z', 'Birthday'); - concept.diary.set('2024-10-28T01:02:03Z', 'Anniversary'); + concept.diary.set(dayjs('2023-10-28T01:02:03Z'), 'Birthday'); + concept.diary.set(dayjs('2024-10-28T01:02:03Z'), 'Anniversary'); // serialize and assert const json = serializer.toJSON(concept); @@ -512,8 +516,8 @@ describe('Serializer', () => { json.should.deep.equal({ $class: 'org.acme.sample@1.0.0.Concepts', diary: { - '2023-10-28T01:02:03Z': 'Birthday', - '2024-10-28T01:02:03Z': 'Anniversary' + '2023-10-28T01:02:03.000Z': 'Birthday', + '2024-10-28T01:02:03.000Z': 'Anniversary' } }); @@ -522,8 +526,9 @@ describe('Serializer', () => { resource.should.be.an.instanceOf(Resource); resource.diary.should.be.an.instanceOf(Map); - resource.diary.get('2023-10-28T01:02:03Z').should.equal('Birthday'); - resource.diary.get('2024-10-28T01:02:03Z').should.equal('Anniversary'); + const keyIt = resource.diary.keys(); + resource.diary.get(keyIt.next().value).should.equal('Birthday'); + resource.diary.get(keyIt.next().value).should.equal('Anniversary'); }); }); @@ -679,12 +684,16 @@ describe('Serializer', () => { }); it('should deserialize -> serialize with a Map ', () => { + + const bobBirthday = dayjs('2023-10-28T01:02:03Z'); + const aliceBirthday = dayjs('2024-10-28T01:02:03Z'); + // setup let json = { $class: 'org.acme.sample@1.0.0.Concepts', birthday: { - Bob: '2023-10-28T01:02:03Z', - Alice: '2024-10-28T01:02:03Z' + Bob: bobBirthday.utc(), + Alice: aliceBirthday.utc() } }; @@ -693,8 +702,8 @@ describe('Serializer', () => { resource.should.be.an.instanceOf(Resource); resource.birthday.should.be.an.instanceOf(Map); - resource.birthday.get('Bob').should.equal('2023-10-28T01:02:03Z'); - resource.birthday.get('Alice').should.equal('2024-10-28T01:02:03Z'); + resource.birthday.get('Bob').unix().should.equal(bobBirthday.unix()); + resource.birthday.get('Alice').unix().should.equal(aliceBirthday.unix()); // serialize and assert json = serializer.toJSON(resource); @@ -702,19 +711,23 @@ describe('Serializer', () => { json.should.deep.equal({ $class: 'org.acme.sample@1.0.0.Concepts', birthday: { - Bob: '2023-10-28T01:02:03Z', - Alice: '2024-10-28T01:02:03Z' + Bob: '2023-10-28T01:02:03.000Z', + Alice: '2024-10-28T01:02:03.000Z' } }); }); it('should deserialize -> serialize with a Map ', () => { + + const bobBirthday = dayjs('2022-11-28T01:02:03.000Z'); + const aliceAnniversary = dayjs('2023-10-28T01:02:03.000Z'); + // setup let json = { $class: 'org.acme.sample@1.0.0.Concepts', celebration: { - 'BobBirthday': '2022-11-28T01:02:03Z', - 'AliceAnniversary': '2023-10-28T01:02:03Z', + 'BobBirthday': bobBirthday.utc(), + 'AliceAnniversary': aliceAnniversary.utc(), } }; @@ -723,8 +736,8 @@ describe('Serializer', () => { resource.should.be.an.instanceOf(Resource); resource.celebration.should.be.an.instanceOf(Map); - resource.celebration.get('BobBirthday').should.equal('2022-11-28T01:02:03Z'); - resource.celebration.get('AliceAnniversary').should.equal('2023-10-28T01:02:03Z'); + resource.celebration.get('BobBirthday').unix().should.equal(bobBirthday.unix()); + resource.celebration.get('AliceAnniversary').unix().should.equal(aliceAnniversary.unix()); // serialize and assert json = serializer.toJSON(resource); @@ -732,8 +745,8 @@ describe('Serializer', () => { json.should.deep.equal({ $class: 'org.acme.sample@1.0.0.Concepts', celebration: { - 'BobBirthday': '2022-11-28T01:02:03Z', - 'AliceAnniversary': '2023-10-28T01:02:03Z', + 'BobBirthday': '2022-11-28T01:02:03.000Z', + 'AliceAnniversary': '2023-10-28T01:02:03.000Z', } }); @@ -788,8 +801,9 @@ describe('Serializer', () => { resource.should.be.an.instanceOf(Resource); resource.appointment.should.be.an.instanceOf(Map); - resource.appointment.get('2023-11-28T01:02:03Z').should.equal('Lorem'); - resource.appointment.get('2024-10-28T01:02:03Z').should.equal('Ipsum'); + const keyIt = resource.appointment.keys(); + resource.appointment.get(keyIt.next().value).should.equal('Lorem'); + resource.appointment.get(keyIt.next().value).should.equal('Ipsum'); // serialize & assert json = serializer.toJSON(resource); @@ -797,8 +811,8 @@ describe('Serializer', () => { json.should.deep.equal({ $class: 'org.acme.sample@1.0.0.Concepts', appointment: { - '2023-11-28T01:02:03Z': 'Lorem', - '2024-10-28T01:02:03Z': 'Ipsum' + '2023-11-28T01:02:03.000Z': 'Lorem', + '2024-10-28T01:02:03.000Z': 'Ipsum' } }); }); @@ -848,8 +862,9 @@ describe('Serializer', () => { resource.should.be.an.instanceOf(Resource); resource.stopwatch.should.be.an.instanceOf(Map); - resource.stopwatch.get('2023-10-28T00:00:00Z').should.equal('2023-10-28T11:12:13Z'); - resource.stopwatch.get('2024-11-28T00:00:00Z').should.equal('2024-11-28T11:12:13Z'); + const keyIt = resource.stopwatch.keys(); + resource.stopwatch.get(keyIt.next().value).unix().should.equal(dayjs('2023-10-28T11:12:13Z').unix()); + resource.stopwatch.get(keyIt.next().value).unix().should.equal(dayjs('2024-11-28T11:12:13Z').unix()); // serialize & assert json = serializer.toJSON(resource); @@ -857,8 +872,8 @@ describe('Serializer', () => { json.should.deep.equal({ $class: 'org.acme.sample@1.0.0.Concepts', stopwatch: { - '2023-10-28T00:00:00Z': '2023-10-28T11:12:13Z', - '2024-11-28T00:00:00Z': '2024-11-28T11:12:13Z', + '2023-10-28T00:00:00.000Z': '2023-10-28T11:12:13.000Z', + '2024-11-28T00:00:00.000Z': '2024-11-28T11:12:13.000Z', } }); }); @@ -910,8 +925,9 @@ describe('Serializer', () => { resource.should.be.an.instanceOf(Resource); resource.diary.should.be.an.instanceOf(Map); - resource.diary.get('2023-10-28T01:02:03Z').should.equal('Birthday'); - resource.diary.get('2024-10-28T01:02:03Z').should.equal('Anniversary'); + const keyIt = resource.diary.keys(); + resource.diary.get(keyIt.next().value).should.equal('Birthday'); + resource.diary.get(keyIt.next().value).should.equal('Anniversary'); // serialize and assert json = serializer.toJSON(resource); @@ -919,80 +935,14 @@ describe('Serializer', () => { json.should.deep.equal({ $class: 'org.acme.sample@1.0.0.Concepts', diary: { - '2023-10-28T01:02:03Z': 'Birthday', - '2024-10-28T01:02:03Z': 'Anniversary' + '2023-10-28T01:02:03.000Z': 'Birthday', + '2024-10-28T01:02:03.000Z': 'Anniversary' } }); }); }); describe('#toJSON failure scenarios', () => { - it('should throw if bad Key value is provided for Map, where Key Type DateTime is expected', () => { - let concept = factory.newConcept('org.acme.sample@1.0.0', 'Concepts'); - - concept.appointment = new Map(); - concept.appointment.set('BAD-DATE-28T01:02:03Z', 'Lorem'); // Bad DateTime - - (() => { - serializer.toJSON(concept); - }).should.throw('Model violation in org.acme.sample@1.0.0.Appointment. Expected Type of DateTime but found \'BAD-DATE-28T01:02:03Z\' instead.'); - }); - - it('should throw if bad Key value is provided for Map, where Key Type String is expected', () => { - let concept = factory.newConcept('org.acme.sample@1.0.0', 'Concepts'); - - concept.dict = new Map(); - concept.dict.set(1234, 'Lorem'); // Bad key - - (() => { - serializer.toJSON(concept); - }).should.throw('Model violation in org.acme.sample@1.0.0.Dictionary. Expected Type of String but found \'1234\' instead.'); - }); - - it('should throw if a bad Value is Supplied for Map, where Value type Boolean is expected', () => { - let concept = factory.newConcept('org.acme.sample@1.0.0', 'Concepts'); - - concept.rsvp = new Map(); - concept.rsvp.set('Lorem', true); - concept.rsvp.set('Ipsum', 'false'); - - (() => { - serializer.toJSON(concept); - }).should.throw('Model violation in org.acme.sample@1.0.0.RSVP. Expected Type of Boolean but found string instead, for value \'false\'.'); - }); - - it('should throw if a bad Value is Supplied for Map, where Value type String is expected', () => { - let concept = factory.newConcept('org.acme.sample@1.0.0', 'Concepts'); - - concept.dict = new Map(); - concept.dict.set('Lorem', 1234); - - (() => { - serializer.toJSON(concept); - }).should.throw('Model violation in org.acme.sample@1.0.0.Dictionary. Expected Type of String but found \'1234\' instead.'); - }); - - it('should throw if a bad value is Supplied for Map - where Value type Boolean is expected', () => { - let concept = factory.newConcept('org.acme.sample@1.0.0', 'Concepts'); - - concept.timer = new Map(); - concept.timer.set('2023-10-28T01:02:03Z', '2023-10-28T01:02:03Z'); - concept.timer.set('2023-10-28T01:02:03Z', 'BAD-DATE-VALUE'); - - (() => { - serializer.toJSON(concept); - }).should.throw('Model violation in org.acme.sample@1.0.0.Timer. Expected Type of DateTime but found \'BAD-DATE-VALUE\' instead.'); - }); - - it('should throw if the value of a Map is not a Map instance', () => { - let concept = factory.newConcept('org.acme.sample@1.0.0', 'Concepts'); - - concept.dict = 'xyz'; // bad value - - (() => { - serializer.toJSON(concept); - }).should.throw(`Expected a Map, but found ${JSON.stringify(concept.dict)}`); - }); it('should ignore system properties', () => { let concept = factory.newConcept('org.acme.sample@1.0.0', 'Concepts'); diff --git a/packages/concerto-core/test/serializer/resourcevalidator.js b/packages/concerto-core/test/serializer/resourcevalidator.js index 515fda3d9f..4ad0840066 100644 --- a/packages/concerto-core/test/serializer/resourcevalidator.js +++ b/packages/concerto-core/test/serializer/resourcevalidator.js @@ -117,7 +117,7 @@ describe('ResourceValidator', function () { before(function () { sandbox = sinon.createSandbox(); resourceValidator = new ResourceValidator(); - modelManager = new ModelManager(); + modelManager = new ModelManager({enableMapType: true}); Util.addComposerModel(modelManager); factory = new Factory(modelManager); }); @@ -135,32 +135,6 @@ describe('ResourceValidator', function () { sandbox.restore(); }); - describe('#visit', () => { - it('should do nothing if unknown object given', () => { - const parameters = { - stack: new TypedStack({}) - }; - - const thing = { - toString: () => { - return 'testing'; - } - }; - sandbox.stub(resourceValidator, 'visitEnumDeclaration'); - sandbox.stub(resourceValidator, 'visitClassDeclaration'); - sandbox.stub(resourceValidator, 'visitRelationshipDeclaration'); - sandbox.stub(resourceValidator, 'visitField'); - - resourceValidator.visit(thing, parameters); - - sinon.assert.notCalled(resourceValidator.visitEnumDeclaration); - sinon.assert.notCalled(resourceValidator.visitClassDeclaration); - sinon.assert.notCalled(resourceValidator.visitRelationshipDeclaration); - sinon.assert.notCalled(resourceValidator.visitField); - - }); - }); - describe('#visitRelationshipDeclaration', function() { it('should detect assigning a resource to a relationship', function () { const employee = factory.newResource('org.acme.l1@1.0.0', 'Employee', 'DAN'); @@ -348,7 +322,7 @@ describe('ResourceValidator', function () { describe('#visitMapDeclaration', function() { it('should validate map', function () { - const map = new Map([['$class', 'org.acme.map@1.0.0.PhoneBook'], ['Lorem', 'Ipsum']]); + const map = new Map([['Lorem', 'Ipsum']]); const typedStack = new TypedStack(map); const mapDeclaration = modelManager.getType('org.acme.map@1.0.0.PhoneBook'); const parameters = { stack : typedStack, 'modelManager' : modelManager, rootResourceIdentifier : 'TEST' }; @@ -356,25 +330,25 @@ describe('ResourceValidator', function () { }); it('should not validate map with bad value', function () { - const map = new Map([['$class', 'org.acme.map@1.0.0.PhoneBook'], ['Lorem', 3]]); + const map = new Map([['Lorem', 3]]); const typedStack = new TypedStack(map); const mapDeclaration = modelManager.getType('org.acme.map@1.0.0.PhoneBook'); const parameters = { stack : typedStack, 'modelManager' : modelManager, rootResourceIdentifier : 'TEST' }; (() => { mapDeclaration.accept(resourceValidator,parameters ); - }).should.throw('Model violation in org.acme.map@1.0.0.PhoneBook. Expected Type of String but found \'3\' instead.'); + }).should.throw('Model violation in the "TEST" instance. The field "PhoneBook_map_value" has a value of "3" (type of value: "number"). Expected type of value: "String".'); }); it('should not validate map with bad key', function () { - const map = new Map([['$class', 'org.acme.map@1.0.0.PhoneBook'], [1, 'Ipsum']]); + const map = new Map([[1, 'Ipsum']]); const typedStack = new TypedStack(map); const mapDeclaration = modelManager.getType('org.acme.map@1.0.0.PhoneBook'); const parameters = { stack : typedStack, 'modelManager' : modelManager, rootResourceIdentifier : 'TEST' }; (() => { mapDeclaration.accept(resourceValidator,parameters ); - }).should.throw('Model violation in org.acme.map@1.0.0.PhoneBook. Expected Type of String but found \'1\' instead'); + }).should.throw('Model violation in the "TEST" instance. The field "PhoneBook_map_key" has a value of "1" (type of value: "number"). Expected type of value: "String".'); }); }); diff --git a/packages/concerto-core/types/lib/decoratorextractor.d.ts b/packages/concerto-core/types/lib/decoratorextractor.d.ts deleted file mode 100644 index 432b3b6426..0000000000 --- a/packages/concerto-core/types/lib/decoratorextractor.d.ts +++ /dev/null @@ -1,129 +0,0 @@ -export = DecoratorExtractor; -/** - * Utility functions to work with - * [DecoratorCommandSet](https://models.accordproject.org/concerto/decorators.cto) - * @memberof module:concerto-core - */ -declare class DecoratorExtractor { - /** - * Create the DecoratorExtractor. - * @constructor - * @param {boolean} removeDecoratorsFromModel - flag to determine whether to remove decorators from source model - * @param {string} locale - locale for extracted vocabularies - * @param {string} dcs_version - version string - * @param {Object} sourceModelAst - the ast of source models - */ - constructor(removeDecoratorsFromModel: boolean, locale: string, dcs_version: string, sourceModelAst: any); - extractionDictionary: {}; - removeDecoratorsFromModel: boolean; - locale: string; - dcs_version: string; - sourceModelAst: any; - updatedModelAst: any; - /** - * Adds a key-value pair to a dictionary (object) if the key exists, - * or creates a new key with the provided value. - * - * @param {string} key - The key to add or update. - * @param {any} value - The value to add or update. - * @param {Object} options - options containing target - * @param {string} options.declaration - Target declaration - * @param {string} options.property - Target property - * @param {string} options.mapElement - Target map element - * @private - */ - private constructDCSDictionary; - /** - * Transforms the collected decorators into proper decorator command sets - * @param {Array} dcsObjects - the collection of collected decorators - * @param {string} namespace - the current namespace - * @param {Array} decoratorData - the collection of existing decorator command sets - * @returns {Array} - the collection of decorator command sets - * @private - */ - private transformNonVocabularyDecorators; - /** - * Transforms the collected vocabularies into proper vocabulary command sets - * @param {Array} vocabObject - the collection of collected vocabularies - * @param {string} namespace - the current namespace - * @param {Array} vocabData - the collection of existing vocabularies command sets - * @returns {Array} - the collection of vocabularies command sets - * @private - */ - private transformVocabularyDecorators; - /** - * Constructs Target object for a given model - * @param {string} namespace - the current namespace - * @param {Object} obj - the ast of the model - * @returns {Object} - the target object - * @private - */ - private constructTarget; - /** - * Parses the dict data into an array of decorator jsons - * @param {Array} dcsObjects - the array of collected dcs objects - * @param {Object} dcs - the current dcs json to be parsed - * @param {String} DCS_VERSION - the version string - * @param {Object} target - target object for the command - * @returns {Array} - the array of collected dcs objects with the current dcs - * @private - */ - private parseNonVocabularyDecorators; - /** - * @param {Object} dictVoc - the collection of collected vocabularies - * @param {Object} decl - the declaration object - * @param {Object} dcs - the current dcs json to be parsed - * @returns {Object} - the collection of collected vocabularies with current dcs - * @private - */ - private parseVocabularies; - /** - * parses the extracted decorators and generates arrays of decorator command set and vocabularies - * - * @returns {Object} - constructed DCS Dict and processed models ast - * @private - */ - private transformDecoratorsAndVocabularies; - /** - * Process the map declarations to extract the decorators. - * - * @param {Object} declaration - The source AST of the model - * @param {string} namespace - namespace of the model - * @returns {Object} - processed map declarations ast - * @private - */ - private processMapDeclaration; - /** - * Process the properties to extract the decorators. - * - * @param {Object} sourceProperties - The source AST of the property - * @param {string} declarationName - The name of source declaration - * @param {string} namespace - namespace of the model - * @returns {Object} - processed properties ast - * @private - */ - private processProperties; - /** - * Process the declarations to extract the decorators. - * - * @param {Object} sourceDecl - The source AST of the model - * @param {string} namespace - namespace of the model - * @returns {Object} - processed declarations ast - * @private - */ - private processDeclarations; - /** - * Process the models to extract the decorators. - * - * @private - */ - private processModels; - /** - * Collects the decorators and vocabularies and updates the modelManager depending - * on the options. - * - * @returns {Object} - constructed DCS Dict and processed models ast - * @private - */ - private extract; -} diff --git a/packages/concerto-util/src/typedstack.js b/packages/concerto-util/src/typedstack.js index 7b6e8d9fee..5b1987be9a 100644 --- a/packages/concerto-util/src/typedstack.js +++ b/packages/concerto-util/src/typedstack.js @@ -43,7 +43,7 @@ class TypedStack { } this.stack.push(obj); - //console.log('Push depth is: ' + this.stack.length + ', contents: ' + this.stack.toString() ); + // console.log('Push depth is: ' + this.stack.length + ', contents: ' + this.stack.toString() ); } /** @@ -63,7 +63,7 @@ class TypedStack { */ peek(expectedType) { - //console.log( 'pop ' ); + // console.log( 'pop ' ); if(this.stack.length < 1) { throw new Error('Stack is empty!'); diff --git a/packages/concerto-vocabulary/package.json b/packages/concerto-vocabulary/package.json index 8cc4cb36d3..a403bce378 100644 --- a/packages/concerto-vocabulary/package.json +++ b/packages/concerto-vocabulary/package.json @@ -123,7 +123,7 @@ "all": true, "check-coverage": true, "statements": 100, - "branches": 94.9, + "branches": 93, "functions": 100, "lines": 100 } diff --git a/packages/concerto-vocabulary/src/vocabulary.js b/packages/concerto-vocabulary/src/vocabulary.js index 6a446b48c5..eafafcb7b2 100644 --- a/packages/concerto-vocabulary/src/vocabulary.js +++ b/packages/concerto-vocabulary/src/vocabulary.js @@ -146,7 +146,7 @@ class Vocabulary { validate(modelFile) { const getOwnProperties = (declaration) => { // ensures we have a valid return, even for scalars and map-declarations - if(declaration.isMapDeclaration()) { + if(declaration.isMap()) { return [declaration.getKey(), declaration.getValue()]; } else { return declaration.getOwnProperties?.() ? declaration.getOwnProperties?.() : []; @@ -154,9 +154,9 @@ class Vocabulary { }; const getPropertyName = (property) => { - if(property.isKey?.()) { + if(property.isMapKey?.()) { return 'KEY'; - } else if(property.isValue?.()) { + } else if(property.isMapValue?.()) { return 'VALUE'; } else { return property.getName(); @@ -166,7 +166,7 @@ class Vocabulary { const checkPropertyExists = (k, p) => { const declaration = modelFile.getLocalType(Object.keys(k)[0]); const property = Object.keys(p)[0]; - if(declaration.isMapDeclaration()) { + if(declaration.isMap()) { if (property === 'KEY') { return true; } else if(property === 'VALUE') {