diff --git a/packages/concerto-core/lib/introspect/classdeclaration.js b/packages/concerto-core/lib/introspect/classdeclaration.js index 18017a2a6..ba97a6aca 100644 --- a/packages/concerto-core/lib/introspect/classdeclaration.js +++ b/packages/concerto-core/lib/introspect/classdeclaration.js @@ -200,6 +200,19 @@ class ClassDeclaration extends Declaration { // if we have a super type make sure it exists if (this.superType !== null) { + // and make sure that the class isn't extending itself + // (an exemption is made for the core classes) + if ( + this.superType === this.name && + ![ + 'Asset', 'Concept', 'Event', 'Participant', 'Transaction', + ].includes(this.superType) + ) { + let formatter = Globalize('en').messageFormatter('classdeclaration-validate-selfextending'); + throw new IllegalModelException(formatter({ + 'class': this.name, + }), this.modelFile, this.ast.location); + } this._resolveSuperType(); } diff --git a/packages/concerto-core/messages/en.json b/packages/concerto-core/messages/en.json index 910b20822..d4004f560 100644 --- a/packages/concerto-core/messages/en.json +++ b/packages/concerto-core/messages/en.json @@ -20,6 +20,7 @@ "classdeclaration-validate-identifiernotstring": "Class \"{class}\" is identified by field \"{idField}\", but the type of the field is not \"String\".", "classdeclaration-validate-duplicatefieldname": "Class \"{class}\" has more than one field named \"{fieldName}\".", "classdeclaration-validate-missingidentifier" : "Class \"{class}\" is not declared as \"abstract\". It must define an identifying field.", + "classdeclaration-validate-selfextending": "Class \"{class}\" cannot extend itself.", "modelfile-constructor-unrecmodelelem": "Unrecognised model element \"{type}\".", "modelfile-resolvetype-undecltype": "Undeclared type \"{type}\" in \"{context}\".", diff --git a/packages/concerto-cto/lib/parser.js b/packages/concerto-cto/lib/parser.js index a41df2eae..903085fc8 100644 --- a/packages/concerto-cto/lib/parser.js +++ b/packages/concerto-cto/lib/parser.js @@ -624,6 +624,9 @@ function peg$parse(input, options) { ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The asset "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -643,6 +646,9 @@ function peg$parse(input, options) { ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The participant "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -662,6 +668,9 @@ function peg$parse(input, options) { ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The transaction "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -681,6 +690,9 @@ function peg$parse(input, options) { ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The event "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -700,6 +712,9 @@ function peg$parse(input, options) { ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The concept "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { diff --git a/packages/concerto-cto/lib/parser.pegjs b/packages/concerto-cto/lib/parser.pegjs index a7d993837..13ef92e75 100644 --- a/packages/concerto-cto/lib/parser.pegjs +++ b/packages/concerto-cto/lib/parser.pegjs @@ -966,6 +966,9 @@ AssetDeclaration ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The asset "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -989,6 +992,9 @@ ParticipantDeclaration ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The participant "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -1012,6 +1018,9 @@ TransactionDeclaration ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The transaction "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -1035,6 +1044,9 @@ EventDeclaration ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The event "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { @@ -1058,6 +1070,9 @@ ConceptDeclaration ...buildRange(location()) }; if (classExtension) { + if (classExtension.name === id.name) { + throw new Error(`The concept "${id.name}" cannot extend itself.`); + } result.superType = classExtension; } if (idField) { diff --git a/packages/concerto-cto/lib/printer.js b/packages/concerto-cto/lib/printer.js index 3d4ab3798..ca00568f5 100644 --- a/packages/concerto-cto/lib/printer.js +++ b/packages/concerto-cto/lib/printer.js @@ -343,6 +343,9 @@ function declFromMetaModel(mm) { } } if (mm.superType) { + if (mm.superType.name === mm.name) { + throw new Error(`The declaration "${mm.name}" cannot extend itself.`); + } result += `extends ${mm.superType.name} `; } result += '{'; diff --git a/packages/concerto-cto/test/cto/bad/self-extending-asset.bad.cto b/packages/concerto-cto/test/cto/bad/self-extending-asset.bad.cto new file mode 100644 index 000000000..504d32c1c --- /dev/null +++ b/packages/concerto-cto/test/cto/bad/self-extending-asset.bad.cto @@ -0,0 +1,5 @@ +namespace com.acme@1.0.0 + +asset Self_Extending extends Self_Extending { + o String foo optional +} diff --git a/packages/concerto-cto/test/cto/bad/self-extending-concept.bad.cto b/packages/concerto-cto/test/cto/bad/self-extending-concept.bad.cto new file mode 100644 index 000000000..09ee02c28 --- /dev/null +++ b/packages/concerto-cto/test/cto/bad/self-extending-concept.bad.cto @@ -0,0 +1,5 @@ +namespace com.acme@1.0.0 + +concept Self_Extending extends Self_Extending { + o String foo optional +} diff --git a/packages/concerto-cto/test/cto/bad/self-extending-event.bad.cto b/packages/concerto-cto/test/cto/bad/self-extending-event.bad.cto new file mode 100644 index 000000000..1905ad5be --- /dev/null +++ b/packages/concerto-cto/test/cto/bad/self-extending-event.bad.cto @@ -0,0 +1,5 @@ +namespace com.acme@1.0.0 + +event Self_Extending extends Self_Extending { + o String foo optional +} diff --git a/packages/concerto-cto/test/cto/bad/self-extending-participant.bad.cto b/packages/concerto-cto/test/cto/bad/self-extending-participant.bad.cto new file mode 100644 index 000000000..58ec17ede --- /dev/null +++ b/packages/concerto-cto/test/cto/bad/self-extending-participant.bad.cto @@ -0,0 +1,5 @@ +namespace com.acme@1.0.0 + +participant Self_Extending extends Self_Extending { + o String foo optional +} diff --git a/packages/concerto-cto/test/cto/bad/self-extending-transaction.bad.cto b/packages/concerto-cto/test/cto/bad/self-extending-transaction.bad.cto new file mode 100644 index 000000000..e87064a98 --- /dev/null +++ b/packages/concerto-cto/test/cto/bad/self-extending-transaction.bad.cto @@ -0,0 +1,5 @@ +namespace com.acme@1.0.0 + +transaction Self_Extending extends Self_Extending { + o String foo optional +} diff --git a/packages/concerto-cto/test/parserMain.js b/packages/concerto-cto/test/parserMain.js index b42c67183..d9be05213 100644 --- a/packages/concerto-cto/test/parserMain.js +++ b/packages/concerto-cto/test/parserMain.js @@ -81,6 +81,24 @@ describe('parser', () => { }); }); + describe('self-extending', () => { + const declarationTypes = [ + 'asset', + 'participant', + 'transaction', + 'event', + 'concept', + ]; + declarationTypes.forEach(declarationType => { + it(`Should not parse a self-extending ${declarationType}`, () => { + let content = fs.readFileSync(`./test/cto/bad/self-extending-${declarationType}.bad.cto`, 'utf8'); + (() => { + Parser.parse(content); + }).should.throw(new RegExp(`The ${declarationType} ".+" cannot extend itself.`)); + }); + }); + }); + describe('identifiers', () => { const acceptedIdentifiers = [ diff --git a/packages/concerto-cto/test/printer.js b/packages/concerto-cto/test/printer.js index d8090f46e..d50fe3456 100644 --- a/packages/concerto-cto/test/printer.js +++ b/packages/concerto-cto/test/printer.js @@ -62,4 +62,30 @@ describe('parser', () => { declarations: [], })).should.throw(Error, 'Unrecognized import'); }); + + it('Should throw error for a self-extending declaration', () => { + (() => Printer.toCTO({ + '$class': 'concerto.metamodel@1.0.0.Model', + 'namespace': 'com.acme@1.0.0', + 'declarations': [ + { + '$class': 'concerto.metamodel@1.0.0.AssetDeclaration', + 'name': 'Self_Extending', + 'isAbstract': false, + 'properties': [ + { + '$class': 'concerto.metamodel@1.0.0.StringProperty', + 'name': 'foo', + 'isArray': false, + 'isOptional': true + } + ], + 'superType': { + '$class': 'concerto.metamodel@1.0.0.TypeIdentifier', + 'name': 'Self_Extending' + } + } + ] + })).should.throw(Error, 'The declaration "Self_Extending" cannot extend itself.'); + }); });