From 144b06da4ca468a793476939acc8fce4d74c75b5 Mon Sep 17 00:00:00 2001 From: Matt Roberts Date: Tue, 28 Feb 2023 13:57:34 +0000 Subject: [PATCH 1/6] feat(tools): add graph and graph search Signed-off-by: Matt Roberts --- .../lib/common/diagramvisitor.js | 208 +++++++++++++++++ packages/concerto-tools/lib/common/graph.js | 220 ++++++++++++++++++ 2 files changed, 428 insertions(+) create mode 100644 packages/concerto-tools/lib/common/diagramvisitor.js create mode 100644 packages/concerto-tools/lib/common/graph.js diff --git a/packages/concerto-tools/lib/common/diagramvisitor.js b/packages/concerto-tools/lib/common/diagramvisitor.js new file mode 100644 index 0000000000..cd84e08bfb --- /dev/null +++ b/packages/concerto-tools/lib/common/diagramvisitor.js @@ -0,0 +1,208 @@ +/* + * 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 ModelUtil = require('@accordproject/concerto-core').ModelUtil; + +/** + * Convert the contents of a ModelManager a diagram format (such as PlantUML or Mermaid) + * Set a fileWriter property (instance of FileWriter) on the parameters + * object to control where the generated code is written to disk. + * + * @private + * @class + */ +class DiagramVisitor { + + /** + * Visitor design pattern + * @param {Object} thing - the object being visited + * @param {Object} parameters - the parameter + * @return {Object} the result of visiting or null + * @private + */ + visit(thing, parameters) { + if (thing.isModelManager?.()) { + return this.visitModelManager(thing, parameters); + } else if (thing.isModelFile?.()) { + return this.visitModelFile(thing, parameters); + } else if (thing.isParticipant?.()) { + return this.visitParticipantDeclaration(thing, parameters); + } else if (thing.isTransaction?.()) { + return this.visitTransactionDeclaration(thing, parameters); + } else if (thing.isEvent?.()) { + return this.visitEventDeclaration(thing, parameters); + } else if (thing.isAsset?.()) { + return this.visitAssetDeclaration(thing, parameters); + } else if (thing.isEnum?.()) { + return this.visitEnumDeclaration(thing, parameters); + } else if (thing.isClassDeclaration?.()) { + return this.visitClassDeclaration(thing, parameters); + } else if (thing.isTypeScalar?.()) { + return this.visitField(thing.getScalarField(), parameters); + } else if (thing.isField?.()) { + return this.visitField(thing, parameters); + } else if (thing.isRelationship?.()) { + return this.visitRelationship(thing, parameters); + } else if (thing.isEnumValue?.()) { + return this.visitEnumValueDeclaration(thing, parameters); + } else if (thing.isScalarDeclaration?.()) { + return; + } else { + throw new Error('Unrecognised ' + JSON.stringify(thing) ); + } + } + + /** + * Visitor design pattern + * @param {ModelManager} modelManager - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + visitModelManager(modelManager, parameters) { + modelManager.getModelFiles().forEach((decl) => { + decl.accept(this, parameters); + }); + } + + /** + * Visitor design pattern + * @param {ModelFile} modelFile - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + visitModelFile(modelFile, parameters) { + modelFile.getAllDeclarations().forEach((decl) => { + decl.accept(this, parameters); + }); + } + + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + visitAssetDeclaration(classDeclaration, parameters) { + this.visitClassDeclaration(classDeclaration, parameters, 'asset'); + } + + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + visitEnumDeclaration(classDeclaration, parameters) { + this.visitClassDeclaration(classDeclaration, parameters, 'enumeration'); + } + + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + visitEventDeclaration(classDeclaration, parameters) { + this.visitClassDeclaration(classDeclaration, parameters, 'event'); + } + + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + visitParticipantDeclaration(classDeclaration, parameters) { + this.visitClassDeclaration(classDeclaration, parameters, 'participant'); + } + + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + visitTransactionDeclaration(classDeclaration, parameters) { + this.visitClassDeclaration(classDeclaration, parameters, 'transaction'); + } + + + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @param {string} type - the type of the declaration + * @private + */ + visitClassDeclaration(classDeclaration, parameters, type = 'concept') { + classDeclaration.getOwnProperties().forEach((property) => { + if (!property.isRelationship?.()) { + property.accept(this, parameters); + } + }); + } + + /** + * Visitor design pattern + * @param {Field} field - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + visitField(field, parameters) { + let array = ''; + + if(field.isArray()) { + array = '[]'; + } + + parameters.fileWriter.writeLine(1, '+ ' + this.escapeString(field.getType() + array) + ' ' + this.escapeString(field.getName())); + } + + /** + * Visitor design pattern + * @param {EnumValueDeclaration} enumValueDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + visitEnumValueDeclaration(enumValueDeclaration, parameters) { + parameters.fileWriter.writeLine(1, '+ ' + this.escapeString(enumValueDeclaration.getName())); + } + + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + writeDeclarationSupertype(classDeclaration, parameters) { + if(classDeclaration.getSuperType()) { + const namespace = ModelUtil.getNamespace(classDeclaration.getSuperType()); + const isBaseModel = ModelUtil.parseNamespace(namespace).name === 'concerto'; + if (parameters.hideBaseModel && isBaseModel){ + return; + } + const source = this.escapeString(classDeclaration.getFullyQualifiedName()); + const target = this.escapeString(classDeclaration.getSuperType()); + parameters.fileWriter.writeLine(0, `${source} ${DiagramVisitor.INHERITANCE} ${target}`); + } + } +} + +DiagramVisitor.COMPOSITION = '*--'; +DiagramVisitor.AGGREGATION = 'o--'; +DiagramVisitor.INHERITANCE = '--|>'; + +module.exports = DiagramVisitor; diff --git a/packages/concerto-tools/lib/common/graph.js b/packages/concerto-tools/lib/common/graph.js new file mode 100644 index 0000000000..c259978bf2 --- /dev/null +++ b/packages/concerto-tools/lib/common/graph.js @@ -0,0 +1,220 @@ +/* eslint-disable require-jsdoc */ +/* + * 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 fs = require('fs'); +const { ModelUtil, ModelManager } = require('@accordproject/concerto-core'); +const { FileWriter } = require('@accordproject/concerto-util'); + +const DiagramVisitor = require('./diagramvisitor'); + +/* + * This class represents a directed graph using an + * adjacency list representation. + */ +class DirectedGraph { + + /** + * Construct a new graph from an adjacency map representation. + * + * Vertices are represented by strings. + * Edges are a list of names of connected vertices + * + * @param {Object.} adjacencyMap - initial graph + */ + constructor(adjacencyMap = {}) { + this.adjacencyMap = adjacencyMap; + } + + /** + * Adds an edge from `source` to `target` + * @param {string} source - the origin of the edge + * @param {*} target - the destination of the edge + */ + addEdge(source, target) { + this.adjacencyMap[source] ??= []; + if (!this.adjacencyMap[source].includes(target)){ + this.adjacencyMap[source].push(target); + } + } + + /** + * Checks if the graph has a named vertex + * @param {string} vertex - the name of the new vertex + * @return {boolean} - true, if the graph has the named vertex + */ + hasVertex(vertex) { + return !!this.adjacencyMap[vertex]; + } + + /** + * Add a vertex to the graph + * @param {string} vertex - the name of the new vertex + */ + addVertex(vertex) { + this.adjacencyMap[vertex] ??= []; + } + + /** + * A utility which traverses this directed graph from the `source` vertex + * to visit all connected vertices to find the maximal subgraph. + * + * This is useful for finding disconnected subgraphs, + * i.e. so-called "tree-shaking". + * + * Optionally supports a list of source vertices, to allow searching from + * multiple start vertices. + * + * Returns a sparsely populated map of vertex names with a flag to indicate + * if the vertex is present in the connected graph. + * + * @param {string | string[]} source - The root vertex (or vertices) from + * which to begin the search + * @returns {DirectedGraph} - A maximal subgraph + */ + findConnectedGraph(source) { + // Normalize the source to an array, even if there's only one + let sourceVertices = source; + if (!Array.isArray(sourceVertices)){ + sourceVertices = [sourceVertices]; + } + + // Track our descent + const visited = {}; + const queue = [...sourceVertices]; + + // Initialize the state + sourceVertices.forEach(v => { visited[v] = true; }); + + // Perform a BFS search of the graph. + let currentVertex; + while (queue.length > 0) { + currentVertex = queue[0]; + queue.shift(); + + const edges = this.adjacencyMap[currentVertex] || []; + + edges.forEach(edge => { + if (!visited[edge]) { + visited[edge] = true; + queue.push(edge); + } + }); + } + + return new DirectedGraph(Object.fromEntries( + Object.entries(this.adjacencyMap) + .filter(([vertex]) => visited[vertex]) + )); + } + + /** + * Visualizes the graph as a Mermaid Flowchart + * + * @param {Writer} writer - Buffer for text to be written + */ + print(writer) { + writer.writeLine(0, 'flowchart LR'); + Object.entries(this.adjacencyMap).forEach(([source, edges]) =>{ + writer.writeLine(1, `\`${source}\``); + (edges || []).forEach(target => { + writer.writeLine(1, `\`${source}\` --> \`${target}\``); + }); + }); + } +} + +/** + * Convert the contents of a ModelManager to a directed graph where types are + * vertices and edges are relationships between types. + * + * @private + * @class + * @memberof module:concerto-util + */ +class ConcertoGraphVisitor extends DiagramVisitor { + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + visitClassDeclaration(classDeclaration, parameters) { + parameters.stack ??= []; + parameters.stack.push(classDeclaration.getFullyQualifiedName()); + parameters.graph.addVertex(classDeclaration.getFullyQualifiedName()); + super.visitClassDeclaration(classDeclaration, parameters); + parameters.stack.pop(); + } + + /** + * Visitor design pattern + * @param {Field} field - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + visitField(field, parameters) { + if (!ModelUtil.isPrimitiveType(field.getFullyQualifiedTypeName())) { + parameters.graph.addEdge(parameters.stack.slice(-1), field.getFullyQualifiedTypeName()); + } + } + + /** + * Visitor design pattern + * @param {Relationship} relationship - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + visitRelationship(relationship, parameters) { + parameters.graph.addEdge(parameters.stack.slice(-1), relationship.getFullyQualifiedTypeName()); + } + + /** + * Visitor design pattern + * @param {EnumValueDeclaration} enumValueDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @private + */ + visitEnumValueDeclaration(enumValueDeclaration, parameters) { + return; + } +} + +module.exports = { + ConcertoGraphVisitor, + DirectedGraph +}; + +const mm = new ModelManager(); +mm.addCTOModel(fs.readFileSync('./lib/common/stripe.cto', 'utf-8')); + +const graph = new DirectedGraph(); +mm.accept(new ConcertoGraphVisitor(), { graph }); + +console.log('Number of concepts', mm.getConceptDeclarations().length); +console.log('Number of models', mm.getModelFiles().length); + +console.log('##### Filtering #####'); +const connectedGraph = graph.findConnectedGraph('com.stripe.action.test@1.0.0.account'); +const filteredModelManager = mm.filter(decorated => connectedGraph.hasVertex(`${decorated.getNamespace()}.${decorated.getName()}`)); + +console.log('Number of concepts', filteredModelManager.getConceptDeclarations().length); +console.log('Number of models', filteredModelManager.getModelFiles().length); + +const writer = new FileWriter(__dirname); +writer.openFile('graph.mmd'); +// graph.print(writer); +connectedGraph.print(writer); +writer.closeFile(); From 87640536bb46361216fc137ca99f826ce5007084 Mon Sep 17 00:00:00 2001 From: Matt Roberts Date: Tue, 28 Feb 2023 14:02:21 +0000 Subject: [PATCH 2/6] feat(core): add changes to core to support graph Signed-off-by: Matt Roberts --- .../concerto-core/lib/basemodelmanager.js | 18 ++ .../concerto-core/lib/introspect/modelfile.js | 22 ++ .../codegen/fromcto/common/diagramvisitor.js | 208 ------------------ .../codegen/fromcto/mermaid/mermaidvisitor.js | 2 +- .../fromcto/plantuml/plantumlvisitor.js | 2 +- packages/concerto-tools/lib/common/graph.js | 35 ++- 6 files changed, 59 insertions(+), 228 deletions(-) delete mode 100644 packages/concerto-tools/lib/codegen/fromcto/common/diagramvisitor.js diff --git a/packages/concerto-core/lib/basemodelmanager.js b/packages/concerto-core/lib/basemodelmanager.js index fd293b1410..82856d872a 100644 --- a/packages/concerto-core/lib/basemodelmanager.js +++ b/packages/concerto-core/lib/basemodelmanager.js @@ -709,6 +709,24 @@ class BaseModelManager { }); return result; } + + /** + * Returns a new ModelManager with only the types for which the + * filter function returns true. + * + * ModelFiles with no declarations after filtering will be removed. + * + * @param {function(Decorated): boolean} predicate - the filter function over a Decorated object + * @returns {ModelManager} - the filtered ModelManager + */ + filter(predicate){ + const modelManager = new BaseModelManager({...this.options}, this.processFile); + const filteredModels = Object.values(this.modelFiles) + .map((modelFile) => modelFile.filter(predicate, modelManager)) + .filter(Boolean); + modelManager.addModelFiles(filteredModels); + return modelManager; + } } module.exports = BaseModelManager; diff --git a/packages/concerto-core/lib/introspect/modelfile.js b/packages/concerto-core/lib/introspect/modelfile.js index 371d81563b..cea2cba247 100644 --- a/packages/concerto-core/lib/introspect/modelfile.js +++ b/packages/concerto-core/lib/introspect/modelfile.js @@ -802,6 +802,28 @@ class ModelFile extends Decorated { } } + /** + * Returns a new ModelFile with only the types for which the + * filter function returns true. + * + * Will return null if the filtered ModelFile doesn't contain any declarations. + * + * @param {function(Decorated): boolean} predicate - the filter function over a Decorated object + * @param {ModelManager} modelManager - the target ModelManager for the filtered ModelFile + * @returns {ModelFile?} - the filtered ModelFile + * @private + */ + filter(predicate, modelManager){ + const declarations = this.declarations?.filter(predicate).map(declaration => declaration.ast); + const ast = { + ...this.ast, + declarations: declarations, + }; + if (ast.declarations?.length > 0){ + return new ModelFile(modelManager, ast, undefined, this.fileName); + } + return null; + } } module.exports = ModelFile; diff --git a/packages/concerto-tools/lib/codegen/fromcto/common/diagramvisitor.js b/packages/concerto-tools/lib/codegen/fromcto/common/diagramvisitor.js deleted file mode 100644 index cd84e08bfb..0000000000 --- a/packages/concerto-tools/lib/codegen/fromcto/common/diagramvisitor.js +++ /dev/null @@ -1,208 +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 ModelUtil = require('@accordproject/concerto-core').ModelUtil; - -/** - * Convert the contents of a ModelManager a diagram format (such as PlantUML or Mermaid) - * Set a fileWriter property (instance of FileWriter) on the parameters - * object to control where the generated code is written to disk. - * - * @private - * @class - */ -class DiagramVisitor { - - /** - * Visitor design pattern - * @param {Object} thing - the object being visited - * @param {Object} parameters - the parameter - * @return {Object} the result of visiting or null - * @private - */ - visit(thing, parameters) { - if (thing.isModelManager?.()) { - return this.visitModelManager(thing, parameters); - } else if (thing.isModelFile?.()) { - return this.visitModelFile(thing, parameters); - } else if (thing.isParticipant?.()) { - return this.visitParticipantDeclaration(thing, parameters); - } else if (thing.isTransaction?.()) { - return this.visitTransactionDeclaration(thing, parameters); - } else if (thing.isEvent?.()) { - return this.visitEventDeclaration(thing, parameters); - } else if (thing.isAsset?.()) { - return this.visitAssetDeclaration(thing, parameters); - } else if (thing.isEnum?.()) { - return this.visitEnumDeclaration(thing, parameters); - } else if (thing.isClassDeclaration?.()) { - return this.visitClassDeclaration(thing, parameters); - } else if (thing.isTypeScalar?.()) { - return this.visitField(thing.getScalarField(), parameters); - } else if (thing.isField?.()) { - return this.visitField(thing, parameters); - } else if (thing.isRelationship?.()) { - return this.visitRelationship(thing, parameters); - } else if (thing.isEnumValue?.()) { - return this.visitEnumValueDeclaration(thing, parameters); - } else if (thing.isScalarDeclaration?.()) { - return; - } else { - throw new Error('Unrecognised ' + JSON.stringify(thing) ); - } - } - - /** - * Visitor design pattern - * @param {ModelManager} modelManager - the object being visited - * @param {Object} parameters - the parameter - * @private - */ - visitModelManager(modelManager, parameters) { - modelManager.getModelFiles().forEach((decl) => { - decl.accept(this, parameters); - }); - } - - /** - * Visitor design pattern - * @param {ModelFile} modelFile - the object being visited - * @param {Object} parameters - the parameter - * @private - */ - visitModelFile(modelFile, parameters) { - modelFile.getAllDeclarations().forEach((decl) => { - decl.accept(this, parameters); - }); - } - - /** - * Visitor design pattern - * @param {ClassDeclaration} classDeclaration - the object being visited - * @param {Object} parameters - the parameter - * @private - */ - visitAssetDeclaration(classDeclaration, parameters) { - this.visitClassDeclaration(classDeclaration, parameters, 'asset'); - } - - /** - * Visitor design pattern - * @param {ClassDeclaration} classDeclaration - the object being visited - * @param {Object} parameters - the parameter - * @private - */ - visitEnumDeclaration(classDeclaration, parameters) { - this.visitClassDeclaration(classDeclaration, parameters, 'enumeration'); - } - - /** - * Visitor design pattern - * @param {ClassDeclaration} classDeclaration - the object being visited - * @param {Object} parameters - the parameter - * @private - */ - visitEventDeclaration(classDeclaration, parameters) { - this.visitClassDeclaration(classDeclaration, parameters, 'event'); - } - - /** - * Visitor design pattern - * @param {ClassDeclaration} classDeclaration - the object being visited - * @param {Object} parameters - the parameter - * @private - */ - visitParticipantDeclaration(classDeclaration, parameters) { - this.visitClassDeclaration(classDeclaration, parameters, 'participant'); - } - - /** - * Visitor design pattern - * @param {ClassDeclaration} classDeclaration - the object being visited - * @param {Object} parameters - the parameter - * @private - */ - visitTransactionDeclaration(classDeclaration, parameters) { - this.visitClassDeclaration(classDeclaration, parameters, 'transaction'); - } - - - /** - * Visitor design pattern - * @param {ClassDeclaration} classDeclaration - the object being visited - * @param {Object} parameters - the parameter - * @param {string} type - the type of the declaration - * @private - */ - visitClassDeclaration(classDeclaration, parameters, type = 'concept') { - classDeclaration.getOwnProperties().forEach((property) => { - if (!property.isRelationship?.()) { - property.accept(this, parameters); - } - }); - } - - /** - * Visitor design pattern - * @param {Field} field - the object being visited - * @param {Object} parameters - the parameter - * @private - */ - visitField(field, parameters) { - let array = ''; - - if(field.isArray()) { - array = '[]'; - } - - parameters.fileWriter.writeLine(1, '+ ' + this.escapeString(field.getType() + array) + ' ' + this.escapeString(field.getName())); - } - - /** - * Visitor design pattern - * @param {EnumValueDeclaration} enumValueDeclaration - the object being visited - * @param {Object} parameters - the parameter - * @private - */ - visitEnumValueDeclaration(enumValueDeclaration, parameters) { - parameters.fileWriter.writeLine(1, '+ ' + this.escapeString(enumValueDeclaration.getName())); - } - - /** - * Visitor design pattern - * @param {ClassDeclaration} classDeclaration - the object being visited - * @param {Object} parameters - the parameter - * @private - */ - writeDeclarationSupertype(classDeclaration, parameters) { - if(classDeclaration.getSuperType()) { - const namespace = ModelUtil.getNamespace(classDeclaration.getSuperType()); - const isBaseModel = ModelUtil.parseNamespace(namespace).name === 'concerto'; - if (parameters.hideBaseModel && isBaseModel){ - return; - } - const source = this.escapeString(classDeclaration.getFullyQualifiedName()); - const target = this.escapeString(classDeclaration.getSuperType()); - parameters.fileWriter.writeLine(0, `${source} ${DiagramVisitor.INHERITANCE} ${target}`); - } - } -} - -DiagramVisitor.COMPOSITION = '*--'; -DiagramVisitor.AGGREGATION = 'o--'; -DiagramVisitor.INHERITANCE = '--|>'; - -module.exports = DiagramVisitor; diff --git a/packages/concerto-tools/lib/codegen/fromcto/mermaid/mermaidvisitor.js b/packages/concerto-tools/lib/codegen/fromcto/mermaid/mermaidvisitor.js index af2586ffbf..1b738d3768 100644 --- a/packages/concerto-tools/lib/codegen/fromcto/mermaid/mermaidvisitor.js +++ b/packages/concerto-tools/lib/codegen/fromcto/mermaid/mermaidvisitor.js @@ -14,7 +14,7 @@ 'use strict'; -const DiagramVisitor = require('../common/diagramvisitor'); +const DiagramVisitor = require('../../../common/diagramvisitor'); /** * Convert the contents of a ModelManager diff --git a/packages/concerto-tools/lib/codegen/fromcto/plantuml/plantumlvisitor.js b/packages/concerto-tools/lib/codegen/fromcto/plantuml/plantumlvisitor.js index fbc6daf94b..2bccab057c 100644 --- a/packages/concerto-tools/lib/codegen/fromcto/plantuml/plantumlvisitor.js +++ b/packages/concerto-tools/lib/codegen/fromcto/plantuml/plantumlvisitor.js @@ -14,7 +14,7 @@ 'use strict'; -const DiagramVisitor = require('../common/diagramvisitor'); +const DiagramVisitor = require('../../../common/diagramvisitor'); /** * Convert the contents of a ModelManager diff --git a/packages/concerto-tools/lib/common/graph.js b/packages/concerto-tools/lib/common/graph.js index c259978bf2..8f40f4ff7f 100644 --- a/packages/concerto-tools/lib/common/graph.js +++ b/packages/concerto-tools/lib/common/graph.js @@ -77,8 +77,7 @@ class DirectedGraph { * Optionally supports a list of source vertices, to allow searching from * multiple start vertices. * - * Returns a sparsely populated map of vertex names with a flag to indicate - * if the vertex is present in the connected graph. + * Returns a new DirectedGraph instance * * @param {string | string[]} source - The root vertex (or vertices) from * which to begin the search @@ -197,24 +196,24 @@ module.exports = { DirectedGraph }; -const mm = new ModelManager(); -mm.addCTOModel(fs.readFileSync('./lib/common/stripe.cto', 'utf-8')); +// const mm = new ModelManager(); +// mm.addCTOModel(fs.readFileSync('./lib/common/stripe.cto', 'utf-8')); -const graph = new DirectedGraph(); -mm.accept(new ConcertoGraphVisitor(), { graph }); +// const graph = new DirectedGraph(); +// mm.accept(new ConcertoGraphVisitor(), { graph }); -console.log('Number of concepts', mm.getConceptDeclarations().length); -console.log('Number of models', mm.getModelFiles().length); +// console.log('Number of concepts', mm.getConceptDeclarations().length); +// console.log('Number of models', mm.getModelFiles().length); -console.log('##### Filtering #####'); -const connectedGraph = graph.findConnectedGraph('com.stripe.action.test@1.0.0.account'); -const filteredModelManager = mm.filter(decorated => connectedGraph.hasVertex(`${decorated.getNamespace()}.${decorated.getName()}`)); +// console.log('##### Filtering #####'); +// const connectedGraph = graph.findConnectedGraph('com.stripe.action.test@1.0.0.account'); +// const filteredModelManager = mm.filter(decorated => connectedGraph.hasVertex(`${decorated.getNamespace()}.${decorated.getName()}`)); -console.log('Number of concepts', filteredModelManager.getConceptDeclarations().length); -console.log('Number of models', filteredModelManager.getModelFiles().length); +// console.log('Number of concepts', filteredModelManager.getConceptDeclarations().length); +// console.log('Number of models', filteredModelManager.getModelFiles().length); -const writer = new FileWriter(__dirname); -writer.openFile('graph.mmd'); -// graph.print(writer); -connectedGraph.print(writer); -writer.closeFile(); +// const writer = new FileWriter(__dirname); +// writer.openFile('graph.mmd'); +// // graph.print(writer); +// connectedGraph.print(writer); +// writer.closeFile(); From a51ebc4f30fd725f326269a1d76ffbb86109728a Mon Sep 17 00:00:00 2001 From: Matt Roberts Date: Tue, 7 Mar 2023 11:53:35 +0000 Subject: [PATCH 3/6] test(tools): graph Signed-off-by: Matt Roberts --- packages/concerto-tools/index.js | 1 + .../codegen/fromcto/mermaid/mermaidvisitor.js | 10 +- .../fromcto/plantuml/plantumlvisitor.js | 18 +-- packages/concerto-tools/lib/common/common.js | 25 ++++ .../lib/common/diagramvisitor.js | 72 +++++++--- packages/concerto-tools/lib/common/graph.js | 84 ++++++----- packages/concerto-tools/test/common/graph.js | 133 ++++++++++++++++++ packages/concerto-tools/types/index.d.ts | 1 + .../codegen/fromcto/csharp/csharpvisitor.d.ts | 10 +- .../fromcto/mermaid/mermaidvisitor.d.ts | 27 +++- .../fromcto/plantuml/plantumlvisitor.d.ts | 51 +++++-- .../types/lib/common/common.d.ts | 4 + .../types/lib/common/diagramvisitor.d.ts | 130 +++++++++++++++++ .../types/lib/common/graph.d.ts | 83 +++++++++++ 14 files changed, 570 insertions(+), 79 deletions(-) create mode 100644 packages/concerto-tools/lib/common/common.js create mode 100644 packages/concerto-tools/test/common/graph.js create mode 100644 packages/concerto-tools/types/lib/common/common.d.ts create mode 100644 packages/concerto-tools/types/lib/common/diagramvisitor.d.ts create mode 100644 packages/concerto-tools/types/lib/common/graph.d.ts diff --git a/packages/concerto-tools/index.js b/packages/concerto-tools/index.js index 9665434372..0e846a9c35 100644 --- a/packages/concerto-tools/index.js +++ b/packages/concerto-tools/index.js @@ -20,4 +20,5 @@ */ module.exports.CodeGen = require('./lib/codegen/codegen'); +module.exports.Common = require('./lib/common/common'); module.exports.version = require('./package.json'); diff --git a/packages/concerto-tools/lib/codegen/fromcto/mermaid/mermaidvisitor.js b/packages/concerto-tools/lib/codegen/fromcto/mermaid/mermaidvisitor.js index 1b738d3768..bb3aa2b9a2 100644 --- a/packages/concerto-tools/lib/codegen/fromcto/mermaid/mermaidvisitor.js +++ b/packages/concerto-tools/lib/codegen/fromcto/mermaid/mermaidvisitor.js @@ -22,7 +22,7 @@ const DiagramVisitor = require('../../../common/diagramvisitor'); * Set a fileWriter property (instance of FileWriter) on the parameters * object to control where the generated code is written to disk. * - * @private + * @protected * @class */ class MermaidVisitor extends DiagramVisitor { @@ -30,7 +30,7 @@ class MermaidVisitor extends DiagramVisitor { * Visitor design pattern * @param {ModelManager} modelManager - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitModelManager(modelManager, parameters) { parameters.fileWriter.openFile('model.mmd'); @@ -46,7 +46,7 @@ class MermaidVisitor extends DiagramVisitor { * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter * @param {string} type - the type of the declaration - * @private + * @protected */ visitClassDeclaration(classDeclaration, parameters, type = 'concept') { if (classDeclaration.getOwnProperties().length > 0) { @@ -82,7 +82,7 @@ class MermaidVisitor extends DiagramVisitor { * Visitor design pattern * @param {RelationshipDeclaration} relationship - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitRelationship(relationship, parameters) { let array = '"1"'; @@ -100,7 +100,7 @@ class MermaidVisitor extends DiagramVisitor { * Escape versions and periods. * @param {String} string - the object being visited * @return {String} string - the parameter - * @private + * @protected */ escapeString(string) { return `\`${string}\``; diff --git a/packages/concerto-tools/lib/codegen/fromcto/plantuml/plantumlvisitor.js b/packages/concerto-tools/lib/codegen/fromcto/plantuml/plantumlvisitor.js index 2bccab057c..f98e9fa40c 100644 --- a/packages/concerto-tools/lib/codegen/fromcto/plantuml/plantumlvisitor.js +++ b/packages/concerto-tools/lib/codegen/fromcto/plantuml/plantumlvisitor.js @@ -22,7 +22,7 @@ const DiagramVisitor = require('../../../common/diagramvisitor'); * Set a fileWriter property (instance of FileWriter) on the parameters * object to control where the generated code is written to disk. * - * @private + * @protected * @class * @memberof module:concerto-tools */ @@ -31,7 +31,7 @@ class PlantUMLVisitor extends DiagramVisitor { * Visitor design pattern * @param {ModelManager} modelManager - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitModelManager(modelManager, parameters) { parameters.fileWriter.openFile('model.puml'); @@ -50,7 +50,7 @@ class PlantUMLVisitor extends DiagramVisitor { * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitAssetDeclaration(classDeclaration, parameters) { this.writeDeclaration(classDeclaration, parameters, '(A,green)' ); @@ -60,7 +60,7 @@ class PlantUMLVisitor extends DiagramVisitor { * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitEnumDeclaration(classDeclaration, parameters) { this.writeDeclaration(classDeclaration, parameters, '(E,grey)' ); @@ -70,7 +70,7 @@ class PlantUMLVisitor extends DiagramVisitor { * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitParticipantDeclaration(classDeclaration, parameters) { this.writeDeclaration(classDeclaration, parameters, '(P,lightblue)' ); @@ -80,7 +80,7 @@ class PlantUMLVisitor extends DiagramVisitor { * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitTransactionDeclaration(classDeclaration, parameters) { this.writeDeclaration(classDeclaration, parameters, '(T,yellow)' ); @@ -91,7 +91,7 @@ class PlantUMLVisitor extends DiagramVisitor { * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitClassDeclaration(classDeclaration, parameters) { this.writeDeclaration(classDeclaration, parameters ); @@ -102,7 +102,7 @@ class PlantUMLVisitor extends DiagramVisitor { * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter * @param {string} [style] - the style for the prototype (optional) - * @private + * @protected */ writeDeclaration(classDeclaration, parameters, style) { parameters.fileWriter.writeLine(0, 'class ' + this.escapeString(classDeclaration.getFullyQualifiedName()) + (style ? ` << ${style} >> ` : ' ') + '{' ); @@ -134,7 +134,7 @@ class PlantUMLVisitor extends DiagramVisitor { * Escape versions and periods. * @param {String} string - the object being visited * @return {String} string - the parameter - * @private + * @protected */ escapeString(string) { return string.replace('@', '_'); diff --git a/packages/concerto-tools/lib/common/common.js b/packages/concerto-tools/lib/common/common.js new file mode 100644 index 0000000000..b0d1132a5c --- /dev/null +++ b/packages/concerto-tools/lib/common/common.js @@ -0,0 +1,25 @@ +/* + * 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 DiagramVisitor = require('./diagramVisitor'); +const { ConcertoGraphVisitor, DirectedGraph } = require('./graph'); + + +module.exports = { + DiagramVisitor, + ConcertoGraphVisitor, + DirectedGraph +}; diff --git a/packages/concerto-tools/lib/common/diagramvisitor.js b/packages/concerto-tools/lib/common/diagramvisitor.js index cd84e08bfb..3aa1cd82a7 100644 --- a/packages/concerto-tools/lib/common/diagramvisitor.js +++ b/packages/concerto-tools/lib/common/diagramvisitor.js @@ -16,12 +16,20 @@ const ModelUtil = require('@accordproject/concerto-core').ModelUtil; +// Types needed for TypeScript generation. +/* eslint-disable no-unused-vars */ +/* istanbul ignore next */ +if (global === undefined) { + const { ModelManager, ModelFile, ClassDeclaration, ScalarDeclaration, Field, EnumValueDeclaration, RelationshipDeclaration} = require('@accordproject/concerto-core'); +} +/* eslint-enable no-unused-vars */ + /** * Convert the contents of a ModelManager a diagram format (such as PlantUML or Mermaid) * Set a fileWriter property (instance of FileWriter) on the parameters * object to control where the generated code is written to disk. * - * @private + * @protected * @class */ class DiagramVisitor { @@ -31,7 +39,7 @@ class DiagramVisitor { * @param {Object} thing - the object being visited * @param {Object} parameters - the parameter * @return {Object} the result of visiting or null - * @private + * @protected */ visit(thing, parameters) { if (thing.isModelManager?.()) { @@ -51,7 +59,7 @@ class DiagramVisitor { } else if (thing.isClassDeclaration?.()) { return this.visitClassDeclaration(thing, parameters); } else if (thing.isTypeScalar?.()) { - return this.visitField(thing.getScalarField(), parameters); + return this.visitScalarField(thing, parameters); } else if (thing.isField?.()) { return this.visitField(thing, parameters); } else if (thing.isRelationship?.()) { @@ -59,7 +67,7 @@ class DiagramVisitor { } else if (thing.isEnumValue?.()) { return this.visitEnumValueDeclaration(thing, parameters); } else if (thing.isScalarDeclaration?.()) { - return; + return this.visitScalarDeclaration(thing, parameters); } else { throw new Error('Unrecognised ' + JSON.stringify(thing) ); } @@ -69,7 +77,7 @@ class DiagramVisitor { * Visitor design pattern * @param {ModelManager} modelManager - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitModelManager(modelManager, parameters) { modelManager.getModelFiles().forEach((decl) => { @@ -81,7 +89,7 @@ class DiagramVisitor { * Visitor design pattern * @param {ModelFile} modelFile - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitModelFile(modelFile, parameters) { modelFile.getAllDeclarations().forEach((decl) => { @@ -93,7 +101,7 @@ class DiagramVisitor { * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitAssetDeclaration(classDeclaration, parameters) { this.visitClassDeclaration(classDeclaration, parameters, 'asset'); @@ -103,7 +111,7 @@ class DiagramVisitor { * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitEnumDeclaration(classDeclaration, parameters) { this.visitClassDeclaration(classDeclaration, parameters, 'enumeration'); @@ -113,7 +121,7 @@ class DiagramVisitor { * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitEventDeclaration(classDeclaration, parameters) { this.visitClassDeclaration(classDeclaration, parameters, 'event'); @@ -123,7 +131,7 @@ class DiagramVisitor { * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitParticipantDeclaration(classDeclaration, parameters) { this.visitClassDeclaration(classDeclaration, parameters, 'participant'); @@ -133,7 +141,7 @@ class DiagramVisitor { * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitTransactionDeclaration(classDeclaration, parameters) { this.visitClassDeclaration(classDeclaration, parameters, 'transaction'); @@ -145,21 +153,49 @@ class DiagramVisitor { * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter * @param {string} type - the type of the declaration - * @private + * @protected */ visitClassDeclaration(classDeclaration, parameters, type = 'concept') { classDeclaration.getOwnProperties().forEach((property) => { - if (!property.isRelationship?.()) { - property.accept(this, parameters); - } + property.accept(this, parameters); }); } + /** + * Visitor design pattern + * @param {ScalarDeclaration} scalarDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + visitScalarDeclaration(scalarDeclaration, parameters) { + return; + } + + /** + * Visitor design pattern + * @param {Field} field - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + visitScalarField(field, parameters) { + this.visitField(field.getScalarField(), parameters); + } + + /** + * Visitor design pattern + * @param {RelationshipDeclaration} relationship - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + visitRelationship(relationship, parameters) { + this.visitField(relationship, parameters); + } + /** * Visitor design pattern * @param {Field} field - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitField(field, parameters) { let array = ''; @@ -175,7 +211,7 @@ class DiagramVisitor { * Visitor design pattern * @param {EnumValueDeclaration} enumValueDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitEnumValueDeclaration(enumValueDeclaration, parameters) { parameters.fileWriter.writeLine(1, '+ ' + this.escapeString(enumValueDeclaration.getName())); @@ -185,7 +221,7 @@ class DiagramVisitor { * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ writeDeclarationSupertype(classDeclaration, parameters) { if(classDeclaration.getSuperType()) { diff --git a/packages/concerto-tools/lib/common/graph.js b/packages/concerto-tools/lib/common/graph.js index 8f40f4ff7f..704b65f2d6 100644 --- a/packages/concerto-tools/lib/common/graph.js +++ b/packages/concerto-tools/lib/common/graph.js @@ -14,12 +14,19 @@ */ 'use strict'; -const fs = require('fs'); -const { ModelUtil, ModelManager } = require('@accordproject/concerto-core'); -const { FileWriter } = require('@accordproject/concerto-util'); +const { ModelUtil } = require('@accordproject/concerto-core'); const DiagramVisitor = require('./diagramvisitor'); +// Types needed for TypeScript generation. +/* eslint-disable no-unused-vars */ +/* istanbul ignore next */ +if (global === undefined) { + const { ClassDeclaration, ScalarDeclaration, Field, EnumValueDeclaration, RelationshipDeclaration} = require('@accordproject/concerto-core'); + const { Writer } = require('@accordproject/concerto-util'); +} +/* eslint-enable no-unused-vars */ + /* * This class represents a directed graph using an * adjacency list representation. @@ -38,10 +45,20 @@ class DirectedGraph { this.adjacencyMap = adjacencyMap; } + /** + * Checks if the graph has an edge from source to target + * @param {string} source - the origin of the edge + * @param {string} target - the destination of the edge + * @return {boolean} - true, if the graph has the edge + */ + hasEdge(source, target) { + return this.adjacencyMap[source].includes(target); + } + /** * Adds an edge from `source` to `target` * @param {string} source - the origin of the edge - * @param {*} target - the destination of the edge + * @param {string} target - the destination of the edge */ addEdge(source, target) { this.adjacencyMap[source] ??= []; @@ -139,7 +156,7 @@ class DirectedGraph { * Convert the contents of a ModelManager to a directed graph where types are * vertices and edges are relationships between types. * - * @private + * @protected * @class * @memberof module:concerto-util */ @@ -148,21 +165,46 @@ class ConcertoGraphVisitor extends DiagramVisitor { * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitClassDeclaration(classDeclaration, parameters) { parameters.stack ??= []; parameters.stack.push(classDeclaration.getFullyQualifiedName()); parameters.graph.addVertex(classDeclaration.getFullyQualifiedName()); + + if (classDeclaration.getSuperType()){ + parameters.graph.addEdge(classDeclaration.getFullyQualifiedName(), classDeclaration.getSuperType()); + } + super.visitClassDeclaration(classDeclaration, parameters); parameters.stack.pop(); } + /** + * Visitor design pattern + * @param {ScalarDeclaration} scalarDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + visitScalarDeclaration(scalarDeclaration, parameters) { + parameters.graph.addVertex(scalarDeclaration.getFullyQualifiedName()); + } + + /** + * Visitor design pattern + * @param {Field} scalar - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + visitScalarField(scalar, parameters) { + parameters.graph.addEdge(parameters.stack.slice(-1), scalar.getFullyQualifiedTypeName()); + } + /** * Visitor design pattern * @param {Field} field - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitField(field, parameters) { if (!ModelUtil.isPrimitiveType(field.getFullyQualifiedTypeName())) { @@ -172,9 +214,9 @@ class ConcertoGraphVisitor extends DiagramVisitor { /** * Visitor design pattern - * @param {Relationship} relationship - the object being visited + * @param {RelationshipDeclaration} relationship - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitRelationship(relationship, parameters) { parameters.graph.addEdge(parameters.stack.slice(-1), relationship.getFullyQualifiedTypeName()); @@ -184,7 +226,7 @@ class ConcertoGraphVisitor extends DiagramVisitor { * Visitor design pattern * @param {EnumValueDeclaration} enumValueDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ visitEnumValueDeclaration(enumValueDeclaration, parameters) { return; @@ -195,25 +237,3 @@ module.exports = { ConcertoGraphVisitor, DirectedGraph }; - -// const mm = new ModelManager(); -// mm.addCTOModel(fs.readFileSync('./lib/common/stripe.cto', 'utf-8')); - -// const graph = new DirectedGraph(); -// mm.accept(new ConcertoGraphVisitor(), { graph }); - -// console.log('Number of concepts', mm.getConceptDeclarations().length); -// console.log('Number of models', mm.getModelFiles().length); - -// console.log('##### Filtering #####'); -// const connectedGraph = graph.findConnectedGraph('com.stripe.action.test@1.0.0.account'); -// const filteredModelManager = mm.filter(decorated => connectedGraph.hasVertex(`${decorated.getNamespace()}.${decorated.getName()}`)); - -// console.log('Number of concepts', filteredModelManager.getConceptDeclarations().length); -// console.log('Number of models', filteredModelManager.getModelFiles().length); - -// const writer = new FileWriter(__dirname); -// writer.openFile('graph.mmd'); -// // graph.print(writer); -// connectedGraph.print(writer); -// writer.closeFile(); diff --git a/packages/concerto-tools/test/common/graph.js b/packages/concerto-tools/test/common/graph.js new file mode 100644 index 0000000000..603e1a5ed5 --- /dev/null +++ b/packages/concerto-tools/test/common/graph.js @@ -0,0 +1,133 @@ +/* + * 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 { DirectedGraph, ConcertoGraphVisitor } = require('../../lib/common/graph.js'); +const { ModelManager } = require('@accordproject/concerto-core'); +const fs = require('fs'); +const { expect } = require('expect'); + +const chai = require('chai'); +const { InMemoryWriter } = require('@accordproject/concerto-util'); +chai.should(); +chai.use(require('chai-as-promised')); +chai.use(require('chai-things')); + +describe('graph', function () { + let modelManager = null; + + beforeEach(function() { + modelManager = new ModelManager(); + const cto = fs.readFileSync('./test/codegen/fromcto/data/model/hr.cto', 'utf-8'); + modelManager.addCTOModel(cto, 'hr.cto'); + }); + + + describe('#visitor', function () { + it('should visit a model manager', function () { + const visitor = new ConcertoGraphVisitor(); + visitor.should.not.be.null; + const writer = new InMemoryWriter(); + + const graph = new DirectedGraph(); + modelManager.accept(visitor, { graph }); + + writer.openFile('graph.mmd'); + graph.print(writer); + writer.closeFile(); + expect(writer.data.get('graph.mmd')).toEqual(`flowchart LR + \`org.acme.hr@1.0.0.State\` + \`org.acme.hr@1.0.0.State\` --> \`concerto@1.0.0.Concept\` + \`org.acme.hr@1.0.0.Address\` + \`org.acme.hr@1.0.0.Address\` --> \`concerto@1.0.0.Concept\` + \`org.acme.hr@1.0.0.Address\` --> \`org.acme.hr@1.0.0.State\` + \`org.acme.hr@1.0.0.Company\` + \`org.acme.hr@1.0.0.Company\` --> \`concerto@1.0.0.Concept\` + \`org.acme.hr@1.0.0.Company\` --> \`org.acme.hr@1.0.0.Address\` + \`org.acme.hr@1.0.0.Department\` + \`org.acme.hr@1.0.0.Department\` --> \`concerto@1.0.0.Concept\` + \`org.acme.hr@1.0.0.Equipment\` + \`org.acme.hr@1.0.0.Equipment\` --> \`concerto@1.0.0.Asset\` + \`org.acme.hr@1.0.0.LaptopMake\` + \`org.acme.hr@1.0.0.LaptopMake\` --> \`concerto@1.0.0.Concept\` + \`org.acme.hr@1.0.0.Laptop\` + \`org.acme.hr@1.0.0.Laptop\` --> \`org.acme.hr@1.0.0.Equipment\` + \`org.acme.hr@1.0.0.Laptop\` --> \`org.acme.hr@1.0.0.LaptopMake\` + \`org.acme.hr@1.0.0.SSN\` + \`org.acme.hr@1.0.0.Person\` + \`org.acme.hr@1.0.0.Person\` --> \`concerto@1.0.0.Participant\` + \`org.acme.hr@1.0.0.Person\` --> \`org.acme.hr@1.0.0.Address\` + \`org.acme.hr@1.0.0.Person\` --> \`org.acme.hr@1.0.0.SSN\` + \`org.acme.hr@1.0.0.Employee\` + \`org.acme.hr@1.0.0.Employee\` --> \`org.acme.hr@1.0.0.Person\` + \`org.acme.hr@1.0.0.Employee\` --> \`org.acme.hr@1.0.0.Department\` + \`org.acme.hr@1.0.0.Employee\` --> \`org.acme.hr@1.0.0.Address\` + \`org.acme.hr@1.0.0.Employee\` --> \`org.acme.hr@1.0.0.Equipment\` + \`org.acme.hr@1.0.0.Employee\` --> \`org.acme.hr@1.0.0.Manager\` + \`org.acme.hr@1.0.0.Contractor\` + \`org.acme.hr@1.0.0.Contractor\` --> \`org.acme.hr@1.0.0.Person\` + \`org.acme.hr@1.0.0.Contractor\` --> \`org.acme.hr@1.0.0.Company\` + \`org.acme.hr@1.0.0.Contractor\` --> \`org.acme.hr@1.0.0.Manager\` + \`org.acme.hr@1.0.0.Manager\` + \`org.acme.hr@1.0.0.Manager\` --> \`org.acme.hr@1.0.0.Employee\` + \`org.acme.hr@1.0.0.Manager\` --> \`org.acme.hr@1.0.0.Person\` + \`org.acme.hr@1.0.0.CompanyEvent\` + \`org.acme.hr@1.0.0.CompanyEvent\` --> \`concerto@1.0.0.Event\` + \`org.acme.hr@1.0.0.Onboarded\` + \`org.acme.hr@1.0.0.Onboarded\` --> \`org.acme.hr@1.0.0.CompanyEvent\` + \`org.acme.hr@1.0.0.Onboarded\` --> \`org.acme.hr@1.0.0.Employee\` + \`org.acme.hr@1.0.0.ChangeOfAddress\` + \`org.acme.hr@1.0.0.ChangeOfAddress\` --> \`concerto@1.0.0.Transaction\` + \`org.acme.hr@1.0.0.ChangeOfAddress\` --> \`org.acme.hr@1.0.0.Person\` + \`org.acme.hr@1.0.0.ChangeOfAddress\` --> \`org.acme.hr@1.0.0.Address\` +`); + }); + + it('should visit find a connected subgraph', function () { + const visitor = new ConcertoGraphVisitor(); + visitor.should.not.be.null; + const writer = new InMemoryWriter(); + + const graph = new DirectedGraph(); + modelManager.accept(visitor, { graph }); + const connectedGraph = graph.findConnectedGraph('org.acme.hr@1.0.0.ChangeOfAddress'); + + const filteredModelManager = modelManager.filter(declaration => connectedGraph.hasVertex(declaration.getFullyQualifiedName())); + + expect(filteredModelManager.getModelFiles()).toHaveLength(1); + expect(filteredModelManager.getModelFiles()[0].getAllDeclarations()).toHaveLength(5); + + writer.openFile('graph.mmd'); + connectedGraph.print(writer); + writer.closeFile(); + expect(writer.data.get('graph.mmd')).toEqual(`flowchart LR + \`org.acme.hr@1.0.0.State\` + \`org.acme.hr@1.0.0.State\` --> \`concerto@1.0.0.Concept\` + \`org.acme.hr@1.0.0.Address\` + \`org.acme.hr@1.0.0.Address\` --> \`concerto@1.0.0.Concept\` + \`org.acme.hr@1.0.0.Address\` --> \`org.acme.hr@1.0.0.State\` + \`org.acme.hr@1.0.0.SSN\` + \`org.acme.hr@1.0.0.Person\` + \`org.acme.hr@1.0.0.Person\` --> \`concerto@1.0.0.Participant\` + \`org.acme.hr@1.0.0.Person\` --> \`org.acme.hr@1.0.0.Address\` + \`org.acme.hr@1.0.0.Person\` --> \`org.acme.hr@1.0.0.SSN\` + \`org.acme.hr@1.0.0.ChangeOfAddress\` + \`org.acme.hr@1.0.0.ChangeOfAddress\` --> \`concerto@1.0.0.Transaction\` + \`org.acme.hr@1.0.0.ChangeOfAddress\` --> \`org.acme.hr@1.0.0.Person\` + \`org.acme.hr@1.0.0.ChangeOfAddress\` --> \`org.acme.hr@1.0.0.Address\` +`); + }); + }); +}); diff --git a/packages/concerto-tools/types/index.d.ts b/packages/concerto-tools/types/index.d.ts index da6d3d797d..b645003c14 100644 --- a/packages/concerto-tools/types/index.d.ts +++ b/packages/concerto-tools/types/index.d.ts @@ -32,4 +32,5 @@ export var CodeGen: { avro: typeof import("./lib/codegen/fromcto/avro/avrovisitor"); }; }; +export var Common: typeof import("./lib/common/common"); export var version: any; diff --git a/packages/concerto-tools/types/lib/codegen/fromcto/csharp/csharpvisitor.d.ts b/packages/concerto-tools/types/lib/codegen/fromcto/csharp/csharpvisitor.d.ts index fbc4dba1a0..0c62b45e46 100644 --- a/packages/concerto-tools/types/lib/codegen/fromcto/csharp/csharpvisitor.d.ts +++ b/packages/concerto-tools/types/lib/codegen/fromcto/csharp/csharpvisitor.d.ts @@ -96,11 +96,12 @@ declare class CSharpVisitor { * @param {string} propertyName the Concerto property name * @param {string} propertyType the Concerto property type * @param {string} array the array declaration + * @param {string} nullableType the nullable expression ? * @param {string} getset the getter and setter declaration * @param {Object} [parameters] - the parameter * @returns {string} the property declaration */ - toCSharpProperty(access: string, parentName: string | undefined, propertyName: string, propertyType: string, array: string, getset: string, parameters?: any): string; + toCSharpProperty(access: string, parentName: string | undefined, propertyName: string, propertyType: string, array: string, nullableType: string, getset: string, parameters?: any): string; /** * Converts a Concerto namespace to a CSharp namespace. If pascal casing is enabled, * each component of the namespace is pascal cased - for example org.example will @@ -145,4 +146,11 @@ declare class CSharpVisitor { * @return {string} the .NET namespace for the model file */ private getDotNetNamespace; + /** + * Get the field type for a given field. + * @private + * @param {Field} field - the object being visited + * @return {string} the type for the field + */ + private getFieldType; } diff --git a/packages/concerto-tools/types/lib/codegen/fromcto/mermaid/mermaidvisitor.d.ts b/packages/concerto-tools/types/lib/codegen/fromcto/mermaid/mermaidvisitor.d.ts index 2fe3c8327b..85a8ee92ae 100644 --- a/packages/concerto-tools/types/lib/codegen/fromcto/mermaid/mermaidvisitor.d.ts +++ b/packages/concerto-tools/types/lib/codegen/fromcto/mermaid/mermaidvisitor.d.ts @@ -5,23 +5,38 @@ export = MermaidVisitor; * Set a fileWriter property (instance of FileWriter) on the parameters * object to control where the generated code is written to disk. * - * @private + * @protected * @class */ declare class MermaidVisitor extends DiagramVisitor { + /** + * Visitor design pattern + * @param {ModelManager} modelManager - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitModelManager(modelManager: ModelManager, parameters: any): void; + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @param {string} type - the type of the declaration + * @protected + */ + protected visitClassDeclaration(classDeclaration: ClassDeclaration, parameters: any, type?: string): void; /** * Visitor design pattern * @param {RelationshipDeclaration} relationship - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ - private visitRelationship; + protected visitRelationship(relationship: RelationshipDeclaration, parameters: any): void; /** * Escape versions and periods. * @param {String} string - the object being visited * @return {String} string - the parameter - * @private + * @protected */ - private escapeString; + protected escapeString(string: string): string; } -import DiagramVisitor = require("../common/diagramvisitor"); +import DiagramVisitor = require("../../../common/diagramvisitor"); diff --git a/packages/concerto-tools/types/lib/codegen/fromcto/plantuml/plantumlvisitor.d.ts b/packages/concerto-tools/types/lib/codegen/fromcto/plantuml/plantumlvisitor.d.ts index 04f56d05ac..aacdc12fb3 100644 --- a/packages/concerto-tools/types/lib/codegen/fromcto/plantuml/plantumlvisitor.d.ts +++ b/packages/concerto-tools/types/lib/codegen/fromcto/plantuml/plantumlvisitor.d.ts @@ -5,32 +5,67 @@ export = PlantUMLVisitor; * Set a fileWriter property (instance of FileWriter) on the parameters * object to control where the generated code is written to disk. * - * @private + * @protected * @class * @memberof module:concerto-tools */ declare class PlantUMLVisitor extends DiagramVisitor { + /** + * Visitor design pattern + * @param {ModelManager} modelManager - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitModelManager(modelManager: ModelManager, parameters: any): void; + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitAssetDeclaration(classDeclaration: ClassDeclaration, parameters: any): void; + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitEnumDeclaration(classDeclaration: ClassDeclaration, parameters: any): void; + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitParticipantDeclaration(classDeclaration: ClassDeclaration, parameters: any): void; + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitTransactionDeclaration(classDeclaration: ClassDeclaration, parameters: any): void; /** * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter - * @private + * @protected */ - private visitClassDeclaration; + protected visitClassDeclaration(classDeclaration: ClassDeclaration, parameters: any): void; /** * Write a class declaration * @param {ClassDeclaration} classDeclaration - the object being visited * @param {Object} parameters - the parameter * @param {string} [style] - the style for the prototype (optional) - * @private + * @protected */ - private writeDeclaration; + protected writeDeclaration(classDeclaration: ClassDeclaration, parameters: any, style?: string): void; /** * Escape versions and periods. * @param {String} string - the object being visited * @return {String} string - the parameter - * @private + * @protected */ - private escapeString; + protected escapeString(string: string): string; } -import DiagramVisitor = require("../common/diagramvisitor"); +import DiagramVisitor = require("../../../common/diagramvisitor"); diff --git a/packages/concerto-tools/types/lib/common/common.d.ts b/packages/concerto-tools/types/lib/common/common.d.ts new file mode 100644 index 0000000000..d95dfa41b5 --- /dev/null +++ b/packages/concerto-tools/types/lib/common/common.d.ts @@ -0,0 +1,4 @@ +import DiagramVisitor = require("./diagramVisitor"); +import { ConcertoGraphVisitor } from "./graph"; +import { DirectedGraph } from "./graph"; +export { DiagramVisitor, ConcertoGraphVisitor, DirectedGraph }; diff --git a/packages/concerto-tools/types/lib/common/diagramvisitor.d.ts b/packages/concerto-tools/types/lib/common/diagramvisitor.d.ts new file mode 100644 index 0000000000..3546997230 --- /dev/null +++ b/packages/concerto-tools/types/lib/common/diagramvisitor.d.ts @@ -0,0 +1,130 @@ +export = DiagramVisitor; +/** + * Convert the contents of a ModelManager a diagram format (such as PlantUML or Mermaid) + * Set a fileWriter property (instance of FileWriter) on the parameters + * object to control where the generated code is written to disk. + * + * @protected + * @class + */ +declare class DiagramVisitor { + /** + * Visitor design pattern + * @param {Object} thing - the object being visited + * @param {Object} parameters - the parameter + * @return {Object} the result of visiting or null + * @protected + */ + protected visit(thing: any, parameters: any): any; + /** + * Visitor design pattern + * @param {ModelManager} modelManager - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitModelManager(modelManager: ModelManager, parameters: any): void; + /** + * Visitor design pattern + * @param {ModelFile} modelFile - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitModelFile(modelFile: ModelFile, parameters: any): void; + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitAssetDeclaration(classDeclaration: ClassDeclaration, parameters: any): void; + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitEnumDeclaration(classDeclaration: ClassDeclaration, parameters: any): void; + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitEventDeclaration(classDeclaration: ClassDeclaration, parameters: any): void; + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitParticipantDeclaration(classDeclaration: ClassDeclaration, parameters: any): void; + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitTransactionDeclaration(classDeclaration: ClassDeclaration, parameters: any): void; + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @param {string} type - the type of the declaration + * @protected + */ + protected visitClassDeclaration(classDeclaration: ClassDeclaration, parameters: any, type?: string): void; + /** + * Visitor design pattern + * @param {ScalarDeclaration} scalarDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitScalarDeclaration(scalarDeclaration: ScalarDeclaration, parameters: any): void; + /** + * Visitor design pattern + * @param {Field} field - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitScalarField(field: Field, parameters: any): void; + /** + * Visitor design pattern + * @param {RelationshipDeclaration} relationship - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitRelationship(relationship: RelationshipDeclaration, parameters: any): void; + /** + * Visitor design pattern + * @param {Field} field - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitField(field: Field, parameters: any): void; + /** + * Visitor design pattern + * @param {EnumValueDeclaration} enumValueDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitEnumValueDeclaration(enumValueDeclaration: EnumValueDeclaration, parameters: any): void; + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected writeDeclarationSupertype(classDeclaration: ClassDeclaration, parameters: any): void; +} +declare namespace DiagramVisitor { + const COMPOSITION: string; + const AGGREGATION: string; + const INHERITANCE: string; +} +import { ModelManager } from "@accordproject/concerto-core"; +import { ModelFile } from "@accordproject/concerto-core"; +import { ClassDeclaration } from "@accordproject/concerto-core"; +import { ScalarDeclaration } from "@accordproject/concerto-core"; +import { Field } from "@accordproject/concerto-core"; +import { RelationshipDeclaration } from "@accordproject/concerto-core"; +import { EnumValueDeclaration } from "@accordproject/concerto-core"; diff --git a/packages/concerto-tools/types/lib/common/graph.d.ts b/packages/concerto-tools/types/lib/common/graph.d.ts new file mode 100644 index 0000000000..52feaef2f4 --- /dev/null +++ b/packages/concerto-tools/types/lib/common/graph.d.ts @@ -0,0 +1,83 @@ +/** + * Convert the contents of a ModelManager to a directed graph where types are + * vertices and edges are relationships between types. + * + * @protected + * @class + * @memberof module:concerto-util + */ +export class ConcertoGraphVisitor extends DiagramVisitor { + /** + * Visitor design pattern + * @param {ClassDeclaration} classDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @protected + */ + protected visitClassDeclaration(classDeclaration: ClassDeclaration, parameters: any): void; +} +export class DirectedGraph { + /** + * Construct a new graph from an adjacency map representation. + * + * Vertices are represented by strings. + * Edges are a list of names of connected vertices + * + * @param {Object.} adjacencyMap - initial graph + */ + constructor(adjacencyMap?: { + [x: string]: string[]; + }); + adjacencyMap: { + [x: string]: string[]; + }; + /** + * Checks if the graph has an edge from source to target + * @param {string} source - the origin of the edge + * @param {string} target - the destination of the edge + * @return {boolean} - true, if the graph has the edge + */ + hasEdge(source: string, target: string): boolean; + /** + * Adds an edge from `source` to `target` + * @param {string} source - the origin of the edge + * @param {string} target - the destination of the edge + */ + addEdge(source: string, target: string): void; + /** + * Checks if the graph has a named vertex + * @param {string} vertex - the name of the new vertex + * @return {boolean} - true, if the graph has the named vertex + */ + hasVertex(vertex: string): boolean; + /** + * Add a vertex to the graph + * @param {string} vertex - the name of the new vertex + */ + addVertex(vertex: string): void; + /** + * A utility which traverses this directed graph from the `source` vertex + * to visit all connected vertices to find the maximal subgraph. + * + * This is useful for finding disconnected subgraphs, + * i.e. so-called "tree-shaking". + * + * Optionally supports a list of source vertices, to allow searching from + * multiple start vertices. + * + * Returns a new DirectedGraph instance + * + * @param {string | string[]} source - The root vertex (or vertices) from + * which to begin the search + * @returns {DirectedGraph} - A maximal subgraph + */ + findConnectedGraph(source: string | string[]): DirectedGraph; + /** + * Visualizes the graph as a Mermaid Flowchart + * + * @param {Writer} writer - Buffer for text to be written + */ + print(writer: Writer): void; +} +import DiagramVisitor = require("./diagramvisitor"); +import { ClassDeclaration } from "@accordproject/concerto-core"; +import { Writer } from "@accordproject/concerto-util"; From 38cb1ec8b356c02f65c8920dedb6c3baa29ab6c1 Mon Sep 17 00:00:00 2001 From: Matt Roberts Date: Tue, 7 Mar 2023 11:54:56 +0000 Subject: [PATCH 4/6] refactor(core): fix generated types. add Declaration class Signed-off-by: Matt Roberts --- packages/concerto-core/index.js | 2 + .../concerto-core/lib/basemodelmanager.js | 3 +- .../lib/introspect/classdeclaration.js | 65 +------ .../lib/introspect/declaration.js | 182 ++++++++++++++++++ .../lib/introspect/scalardeclaration.js | 126 ++++-------- packages/concerto-core/lib/modelutil.js | 8 +- .../test/introspect/scalardeclaration.js | 6 +- packages/concerto-core/test/modelmanager.js | 15 ++ packages/concerto-core/types/index.d.ts | 3 +- .../types/lib/basemodelmanager.d.ts | 11 ++ .../lib/introspect/classdeclaration.d.ts | 67 +------ .../types/lib/introspect/declaration.d.ts | 89 +++++++++ .../types/lib/introspect/modelfile.d.ts | 12 ++ .../lib/introspect/scalardeclaration.d.ts | 94 ++------- .../concerto-core/types/lib/modelutil.d.ts | 2 +- .../codegen/__snapshots__/codegen.js.snap | 40 ++++ 16 files changed, 416 insertions(+), 309 deletions(-) create mode 100644 packages/concerto-core/lib/introspect/declaration.js create mode 100644 packages/concerto-core/types/lib/introspect/declaration.d.ts diff --git a/packages/concerto-core/index.js b/packages/concerto-core/index.js index 62fbc8bb07..5dea28f854 100644 --- a/packages/concerto-core/index.js +++ b/packages/concerto-core/index.js @@ -39,6 +39,7 @@ const EnumValueDeclaration = require('./lib/introspect/enumvaluedeclaration'); const EventDeclaration = require('./lib/introspect/eventdeclaration'); const ParticipantDeclaration = require('./lib/introspect/participantdeclaration'); const TransactionDeclaration = require('./lib/introspect/transactiondeclaration'); +const ScalarDeclaration = require('./lib/introspect/scalarDeclaration'); // Properties const Property = require('./lib/introspect/property'); @@ -114,6 +115,7 @@ module.exports = { EventDeclaration, ParticipantDeclaration, TransactionDeclaration, + ScalarDeclaration, Property, Field, EnumDeclaration, diff --git a/packages/concerto-core/lib/basemodelmanager.js b/packages/concerto-core/lib/basemodelmanager.js index 82856d872a..8c1152f9f7 100644 --- a/packages/concerto-core/lib/basemodelmanager.js +++ b/packages/concerto-core/lib/basemodelmanager.js @@ -32,6 +32,7 @@ const { getRootModel } = require('./rootmodel'); /* eslint-disable no-unused-vars */ /* istanbul ignore next */ if (global === undefined) { + const Decorated = require('./introspect/decorated'); const AssetDeclaration = require('./introspect/assetdeclaration'); const ClassDeclaration = require('./introspect/classdeclaration'); const ConceptDeclaration = require('./introspect/conceptdeclaration'); @@ -717,7 +718,7 @@ class BaseModelManager { * ModelFiles with no declarations after filtering will be removed. * * @param {function(Decorated): boolean} predicate - the filter function over a Decorated object - * @returns {ModelManager} - the filtered ModelManager + * @returns {BaseModelManager} - the filtered ModelManager */ filter(predicate){ const modelManager = new BaseModelManager({...this.options}, this.processFile); diff --git a/packages/concerto-core/lib/introspect/classdeclaration.js b/packages/concerto-core/lib/introspect/classdeclaration.js index 171230e09b..7b9df5b222 100644 --- a/packages/concerto-core/lib/introspect/classdeclaration.js +++ b/packages/concerto-core/lib/introspect/classdeclaration.js @@ -16,20 +16,18 @@ const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); -const Decorated = require('./decorated'); +const Declaration = require('./declaration'); const EnumValueDeclaration = require('./enumvaluedeclaration'); const Field = require('./field'); const Globalize = require('../globalize'); const IllegalModelException = require('./illegalmodelexception'); const Introspector = require('./introspector'); -const ModelUtil = require('../modelutil'); const RelationshipDeclaration = require('./relationshipdeclaration'); // Types needed for TypeScript generation. /* eslint-disable no-unused-vars */ /* istanbul ignore next */ if (global === undefined) { - const ModelFile = require('./modelfile'); const Property = require('./property'); } /* eslint-enable no-unused-vars */ @@ -45,31 +43,7 @@ if (global === undefined) { * @class * @memberof module:concerto-core */ -class ClassDeclaration extends Decorated { - /** - * 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(ast); - this.modelFile = modelFile; - this.process(); - } - - /** - * Returns the ModelFile that defines this class. - * - * @public - * @return {ModelFile} the owning ModelFile - */ - getModelFile() { - return this.modelFile; - } - +class ClassDeclaration extends Declaration { /** * Process the AST and build the model * @@ -79,13 +53,8 @@ class ClassDeclaration extends Decorated { process() { super.process(); - if (!ModelUtil.isValidIdentifier(this.ast.name)){ - throw new IllegalModelException(`Invalid class name '${this.ast.name}'`, this.modelFile, this.ast.location); - } - const reservedProperties = ['$class', '$identifier', '$timestamp']; - this.name = this.ast.name; this.properties = []; this.superType = null; this.superTypeDeclaration = null; @@ -143,8 +112,6 @@ class ClassDeclaration extends Decorated { } } - this.fqn = ModelUtil.getFullyQualifiedName(this.modelFile.getNamespace(), this.name); - if (this.fqn === 'concerto@1.0.0.Transaction' || this.fqn === 'concerto@1.0.0.Event') { this.addTimestampField(); } @@ -332,34 +299,6 @@ class ClassDeclaration extends Decorated { return this.abstract; } - /** - * 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; - } - - /** - * Return the namespace of this class. - * @return {string} namespace - a namespace. - */ - getNamespace() { - return this.modelFile.getNamespace(); - } - - /** - * 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 true if this class declaration declares an identifying field * (system or explicit) diff --git a/packages/concerto-core/lib/introspect/declaration.js b/packages/concerto-core/lib/introspect/declaration.js new file mode 100644 index 0000000000..0bd8b59e5c --- /dev/null +++ b/packages/concerto-core/lib/introspect/declaration.js @@ -0,0 +1,182 @@ +/* + * 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 Decorated = require('./decorated'); +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 */ + +/** + * 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. + * + * @abstract + * @class + * @memberof module:concerto-core + */ +class Declaration extends Decorated { + /** + * Create a Declaration 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(ast); + this.modelFile = modelFile; + 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); + } + + /** + * 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; + } + + /** + * Return the namespace of this class. + * @return {string} namespace - a namespace. + */ + getNamespace() { + return this.modelFile.getNamespace(); + } + + /** + * 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 false as scalars are never identified. + * @returns {Boolean} false as scalars are never identified + */ + isIdentified() { + return false; + } + + /** + * Returns false as scalars are never identified. + * @returns {Boolean} false as scalars are never identified + */ + isSystemIdentified() { + return false; + } + + /** + * Returns the name of the identifying field for this class. Note + * that the identifying field may come from a super type. + * + * @return {string} the name of the id field for this class or null if it does not exist + */ + getIdentifierFieldName() { + return null; + } + + /** + * 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 + */ + getType() { + return null; + } + + /** + * Returns the string representation of this class + * @return {String} the string representation of the class + */ + 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; + } +} + +module.exports = Declaration; diff --git a/packages/concerto-core/lib/introspect/scalardeclaration.js b/packages/concerto-core/lib/introspect/scalardeclaration.js index c422147583..144c1ee1ba 100644 --- a/packages/concerto-core/lib/introspect/scalardeclaration.js +++ b/packages/concerto-core/lib/introspect/scalardeclaration.js @@ -16,9 +16,8 @@ const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); -const Decorated = require('./decorated'); +const Declaration = require('./declaration'); const IllegalModelException = require('./illegalmodelexception'); -const ModelUtil = require('../modelutil'); const NumberValidator = require('./numbervalidator'); const StringValidator = require('./stringvalidator'); @@ -26,8 +25,8 @@ const StringValidator = require('./stringvalidator'); /* eslint-disable no-unused-vars */ /* istanbul ignore next */ if (global === undefined) { - const ModelFile = require('./modelfile'); const Validator = require('./validator'); + const ClassDeclaration = require('./classdeclaration'); } /* eslint-enable no-unused-vars */ @@ -42,31 +41,7 @@ if (global === undefined) { * @class * @memberof module:concerto-core */ -class ScalarDeclaration extends Decorated { - /** - * Create a ScalarDeclaration 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(ast); - this.modelFile = modelFile; - this.process(); - } - - /** - * Returns the ModelFile that defines this class. - * - * @public - * @return {ModelFile} the owning ModelFile - */ - getModelFile() { - return this.modelFile; - } - +class ScalarDeclaration extends Declaration { /** * Process the AST and build the model * @@ -76,7 +51,6 @@ class ScalarDeclaration extends Decorated { process() { super.process(); - this.name = this.ast.name; this.superType = null; this.superTypeDeclaration = null; this.idField = null; @@ -120,8 +94,6 @@ class ScalarDeclaration extends Decorated { } else { this.defaultValue = null; } - - this.fqn = ModelUtil.getFullyQualifiedName(this.modelFile.getNamespace(), this.name); } /** @@ -151,37 +123,10 @@ class ScalarDeclaration extends Decorated { } } - /** - * 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; - } - - /** - * Return the namespace of this class. - * @return {string} namespace - a namespace. - */ - getNamespace() { - return this.modelFile.getNamespace(); - } - - /** - * 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 false as scalars are never identified. * @returns {Boolean} false as scalars are never identified + * @deprecated */ isIdentified() { return false; @@ -190,6 +135,7 @@ class ScalarDeclaration extends Decorated { /** * Returns false as scalars are never identified. * @returns {Boolean} false as scalars are never identified + * @deprecated */ isSystemIdentified() { return false; @@ -197,13 +143,13 @@ class ScalarDeclaration extends Decorated { /** * Returns null as scalars are never identified. - * @return {null} 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. @@ -215,15 +161,20 @@ class ScalarDeclaration extends Decorated { } /** - * Throws an error as scalars do not have supertypes. + * 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() { - throw new Error('Scalars do not have super types.'); + return null; } /** * Get the super type class declaration for this class. - * @return {ScalarDeclaration | null} the super type declaration, or null if there is no super type. + * @return {ClassDeclaration} the super type declaration, or null if there is no super type. + * @deprecated */ getSuperTypeDeclaration() { return null; @@ -238,9 +189,9 @@ class ScalarDeclaration extends Decorated { } /** - * Returns the default value for the field or null - * @return {string | number | null} the default value for the field or null - */ + * Returns the default value for the field or null + * @return {string | number | null} the default value for the field or null + */ getDefaultValue() { if(this.defaultValue) { return this.defaultValue; @@ -262,15 +213,26 @@ class ScalarDeclaration extends Decorated { * 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; @@ -280,6 +242,7 @@ class ScalarDeclaration extends Decorated { * Returns true if this class is the definition of a participant. * * @return {boolean} true if the class is a participant + * @deprecated */ isParticipant() { return false; @@ -289,6 +252,7 @@ class ScalarDeclaration extends Decorated { * Returns true if this class is the definition of a transaction. * * @return {boolean} true if the class is a transaction + * @deprecated */ isTransaction() { return false; @@ -298,6 +262,7 @@ class ScalarDeclaration extends Decorated { * Returns true if this class is the definition of an event. * * @return {boolean} true if the class is an event + * @deprecated */ isEvent() { return false; @@ -307,37 +272,12 @@ class ScalarDeclaration extends Decorated { * Returns true if this class is the definition of a concept. * * @return {boolean} true if the class is a concept + * @deprecated */ isConcept() { return false; } - /** - * 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 true; - } } module.exports = ScalarDeclaration; diff --git a/packages/concerto-core/lib/modelutil.js b/packages/concerto-core/lib/modelutil.js index b426cd77b1..ff34217ff1 100644 --- a/packages/concerto-core/lib/modelutil.js +++ b/packages/concerto-core/lib/modelutil.js @@ -174,13 +174,13 @@ class ModelUtil { /** * Returns the true if the given field is a Scalar type - * @param {Scalar} scalar - the string + * @param {Field} field - the Field to test * @return {boolean} true if the field is declared as an scalar * @private */ - static isScalar(scalar) { - const modelFile = scalar.getParent().getModelFile(); - const declaration = modelFile.getType(scalar.getType()); + static isScalar(field) { + const modelFile = field.getParent().getModelFile(); + const declaration = modelFile.getType(field.getType()); return (declaration !== null && declaration.isScalarDeclaration?.()); } diff --git a/packages/concerto-core/test/introspect/scalardeclaration.js b/packages/concerto-core/test/introspect/scalardeclaration.js index 54b68f1afe..2e05ff0ede 100644 --- a/packages/concerto-core/test/introspect/scalardeclaration.js +++ b/packages/concerto-core/test/introspect/scalardeclaration.js @@ -102,11 +102,9 @@ describe('ScalarDeclaration', () => { const modelFiles = introspectUtils.loadModelFiles([modelFileName], modelManager); modelManager.addModelFiles(modelFiles); }); - it('should throw an error', () => { + it('should return null', () => { const testClass = modelManager.getType('com.testing.SSN'); - (() => { - testClass.getSuperType(); - }).should.throw('Scalars do not have super types.'); + should.equal(testClass.getSuperType(), null); }); }); diff --git a/packages/concerto-core/test/modelmanager.js b/packages/concerto-core/test/modelmanager.js index 95b3d54c3d..ef6e115dfc 100644 --- a/packages/concerto-core/test/modelmanager.js +++ b/packages/concerto-core/test/modelmanager.js @@ -1046,4 +1046,19 @@ concept Bar { }); }); + describe('#filter', () => { + it('should return true for a valid ModelManager', () => { + modelManager.addModelFiles([composerModel, modelBase, farm2fork, concertoModel]); + + modelManager.getModelFiles().length.should.equal(5); + modelManager.getModelFiles().map(mf => mf.getAllDeclarations()).flat().length.should.equal(68); + + const filtered = modelManager.filter(declaration => declaration.getFullyQualifiedName() === 'org.accordproject.test.Product'); + + filtered.getModelFiles().length.should.equal(1); + filtered.getModelFiles().map(mf => mf.getAllDeclarations()).flat().length.should.equal(1); + + filtered.validateModelFiles(); + }); + }); }); diff --git a/packages/concerto-core/types/index.d.ts b/packages/concerto-core/types/index.d.ts index ea0b78889d..7cb8f1652c 100644 --- a/packages/concerto-core/types/index.d.ts +++ b/packages/concerto-core/types/index.d.ts @@ -12,6 +12,7 @@ import EnumValueDeclaration = require("./lib/introspect/enumvaluedeclaration"); import EventDeclaration = require("./lib/introspect/eventdeclaration"); import ParticipantDeclaration = require("./lib/introspect/participantdeclaration"); import TransactionDeclaration = require("./lib/introspect/transactiondeclaration"); +import ScalarDeclaration = require("./lib/introspect/scalarDeclaration"); import Property = require("./lib/introspect/property"); import Field = require("./lib/introspect/field"); import EnumDeclaration = require("./lib/introspect/enumdeclaration"); @@ -39,4 +40,4 @@ export const version: { name: string; version: string; }; -export { SecurityException, IllegalModelException, TypeNotFoundException, Decorator, DecoratorFactory, DecoratorManager, ClassDeclaration, IdentifiedDeclaration, AssetDeclaration, ConceptDeclaration, EnumValueDeclaration, EventDeclaration, ParticipantDeclaration, TransactionDeclaration, Property, Field, EnumDeclaration, RelationshipDeclaration, Validator, NumberValidator, StringValidator, Typed, Identifiable, Relationship, Resource, Factory, Globalize, Introspector, ModelFile, ModelManager, Serializer, ModelUtil, ModelLoader, DateTimeUtil, Concerto, MetaModel }; +export { SecurityException, IllegalModelException, TypeNotFoundException, Decorator, DecoratorFactory, DecoratorManager, ClassDeclaration, IdentifiedDeclaration, AssetDeclaration, ConceptDeclaration, EnumValueDeclaration, EventDeclaration, ParticipantDeclaration, TransactionDeclaration, ScalarDeclaration, Property, Field, EnumDeclaration, RelationshipDeclaration, Validator, NumberValidator, StringValidator, Typed, Identifiable, Relationship, Resource, Factory, Globalize, Introspector, ModelFile, ModelManager, Serializer, ModelUtil, ModelLoader, DateTimeUtil, Concerto, MetaModel }; diff --git a/packages/concerto-core/types/lib/basemodelmanager.d.ts b/packages/concerto-core/types/lib/basemodelmanager.d.ts index 7078a1d4c8..5bae265fbd 100644 --- a/packages/concerto-core/types/lib/basemodelmanager.d.ts +++ b/packages/concerto-core/types/lib/basemodelmanager.d.ts @@ -295,6 +295,16 @@ declare class BaseModelManager { * @returns {*} the metamodel */ getAst(resolve?: boolean): any; + /** + * Returns a new ModelManager with only the types for which the + * filter function returns true. + * + * ModelFiles with no declarations after filtering will be removed. + * + * @param {function(Decorated): boolean} predicate - the filter function over a Decorated object + * @returns {BaseModelManager} - the filtered ModelManager + */ + filter(predicate: (arg0: Decorated) => boolean): BaseModelManager; } import ModelFile = require("./introspect/modelfile"); import { FileDownloader } from "@accordproject/concerto-util"; @@ -308,3 +318,4 @@ import ConceptDeclaration = require("./introspect/conceptdeclaration"); import Factory = require("./factory"); import Serializer = require("./serializer"); import DecoratorFactory = require("./introspect/decoratorfactory"); +import Decorated = require("./introspect/decorated"); diff --git a/packages/concerto-core/types/lib/introspect/classdeclaration.d.ts b/packages/concerto-core/types/lib/introspect/classdeclaration.d.ts index 44ddbb6943..d94496be1a 100644 --- a/packages/concerto-core/types/lib/introspect/classdeclaration.d.ts +++ b/packages/concerto-core/types/lib/introspect/classdeclaration.d.ts @@ -10,18 +10,7 @@ export = ClassDeclaration; * @class * @memberof module:concerto-core */ -declare class ClassDeclaration extends Decorated { - /** - * 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: ModelFile, ast: any); - modelFile: ModelFile; - name: any; +declare class ClassDeclaration extends Declaration { properties: any[]; superType: any; superTypeDeclaration: any; @@ -29,7 +18,6 @@ declare class ClassDeclaration extends Decorated { timestamped: boolean; abstract: boolean; type: any; - fqn: string; /** * Adds a required field named 'timestamp' of type 'DateTime' if this class declaration has the 'concerto.Concept' * super type. @@ -63,49 +51,11 @@ declare class ClassDeclaration extends Decorated { * @return {boolean} true if the class is abstract */ isAbstract(): boolean; - /** - * 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(): string; - /** - * Return the namespace of this class. - * @return {string} namespace - a namespace. - */ - getNamespace(): string; - /** - * 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(): string; - /** - * Returns true if this class declaration declares an identifying field - * (system or explicit) - * @returns {Boolean} true if the class declaration includes an identifier - */ - isIdentified(): boolean; - /** - * Returns true if this class declaration declares a system identifier - * $identifier - * @returns {Boolean} true if the class declaration includes a system identifier - */ - isSystemIdentified(): boolean; /** * Returns true if this class declaration declares an explicit identifier * @returns {Boolean} true if the class declaration includes an explicit identifier */ isExplicitlyIdentified(): boolean; - /** - * Returns the name of the identifying field for this class. Note - * that the identifying field may come from a super type. - * - * @return {string} the name of the id field for this class or null if it does not exist - */ - getIdentifierFieldName(): string; /** * Returns the field with a given name or null if it does not exist. * The field must be directly owned by this class -- the super-type is @@ -199,19 +149,6 @@ declare class ClassDeclaration extends Decorated { * @return {boolean} true if the class is an asset */ isConcept(): boolean; - /** - * Returns true if this class is the definition of a enum. - * - * @return {boolean} true if the class is an asset - */ - isEnum(): boolean; - /** - * Returns true if this class is the definition of a enum. - * - * @return {boolean} true if the class is an asset - */ - isClassDeclaration(): boolean; } -import Decorated = require("./decorated"); -import ModelFile = require("./modelfile"); +import Declaration = require("./declaration"); import Property = require("./property"); diff --git a/packages/concerto-core/types/lib/introspect/declaration.d.ts b/packages/concerto-core/types/lib/introspect/declaration.d.ts new file mode 100644 index 0000000000..9176e10680 --- /dev/null +++ b/packages/concerto-core/types/lib/introspect/declaration.d.ts @@ -0,0 +1,89 @@ +export = Declaration; +/** + * 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. + * + * @abstract + * @class + * @memberof module:concerto-core + */ +declare class Declaration extends Decorated { + /** + * Create a Declaration 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: ModelFile, ast: any); + modelFile: ModelFile; + name: any; + fqn: string; + /** + * 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(): string; + /** + * Return the namespace of this class. + * @return {string} namespace - a namespace. + */ + getNamespace(): string; + /** + * 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(): string; + /** + * Returns false as scalars are never identified. + * @returns {Boolean} false as scalars are never identified + */ + isIdentified(): boolean; + /** + * Returns false as scalars are never identified. + * @returns {Boolean} false as scalars are never identified + */ + isSystemIdentified(): boolean; + /** + * Returns the name of the identifying field for this class. Note + * that the identifying field may come from a super type. + * + * @return {string} the name of the id field for this class or null if it does not exist + */ + getIdentifierFieldName(): string; + /** + * 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 + */ + getType(): string; + /** + * Returns true if this class is the definition of an enum. + * + * @return {boolean} true if the class is an enum + */ + isEnum(): boolean; + /** + * Returns true if this class is the definition of a class declaration. + * + * @return {boolean} true if the class is a class + */ + isClassDeclaration(): boolean; + /** + * Returns true if this class is the definition of a scalar declaration. + * + * @return {boolean} true if the class is a scalar + */ + isScalarDeclaration(): boolean; +} +import Decorated = require("./decorated"); +import ModelFile = require("./modelfile"); diff --git a/packages/concerto-core/types/lib/introspect/modelfile.d.ts b/packages/concerto-core/types/lib/introspect/modelfile.d.ts index 7d05f95846..8b9bc5b207 100644 --- a/packages/concerto-core/types/lib/introspect/modelfile.d.ts +++ b/packages/concerto-core/types/lib/introspect/modelfile.d.ts @@ -259,6 +259,18 @@ declare class ModelFile extends Decorated { */ private fromAst; namespace: any; + /** + * Returns a new ModelFile with only the types for which the + * filter function returns true. + * + * Will return null if the filtered ModelFile doesn't contain any declarations. + * + * @param {function(Decorated): boolean} predicate - the filter function over a Decorated object + * @param {ModelManager} modelManager - the target ModelManager for the filtered ModelFile + * @returns {ModelFile?} - the filtered ModelFile + * @private + */ + private filter; } import Decorated = require("./decorated"); import ModelManager = require("../modelmanager"); diff --git a/packages/concerto-core/types/lib/introspect/scalardeclaration.d.ts b/packages/concerto-core/types/lib/introspect/scalardeclaration.d.ts index 86934afb53..aa0e51908c 100644 --- a/packages/concerto-core/types/lib/introspect/scalardeclaration.d.ts +++ b/packages/concerto-core/types/lib/introspect/scalardeclaration.d.ts @@ -10,18 +10,7 @@ export = ScalarDeclaration; * @class * @memberof module:concerto-core */ -declare class ScalarDeclaration extends Decorated { - /** - * Create a ScalarDeclaration 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: ModelFile, ast: any); - modelFile: ModelFile; - name: any; +declare class ScalarDeclaration extends Declaration { superType: any; superTypeDeclaration: any; idField: any; @@ -30,7 +19,6 @@ declare class ScalarDeclaration extends Decorated { validator: StringValidator | NumberValidator; type: string; defaultValue: any; - fqn: string; /** * Semantic validation of the structure of this class. Subclasses should * override this method to impose additional semantic constraints on the @@ -40,123 +28,75 @@ declare class ScalarDeclaration extends Decorated { * @protected */ protected validate(): void; - /** - * 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(): string; - /** - * Return the namespace of this class. - * @return {string} namespace - a namespace. - */ - getNamespace(): string; - /** - * 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(): string; - /** - * Returns false as scalars are never identified. - * @returns {Boolean} false as scalars are never identified - */ - isIdentified(): boolean; - /** - * Returns false as scalars are never identified. - * @returns {Boolean} false as scalars are never identified - */ - isSystemIdentified(): boolean; - /** - * Returns null as scalars are never identified. - * @return {null} null, as scalars are never identified - */ - getIdentifierFieldName(): null; /** * 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 */ - getType(): string; - /** - * Throws an error as scalars do not have supertypes. - */ - getSuperType(): void; + getSuperType(): string; /** * Get the super type class declaration for this class. - * @return {ScalarDeclaration | null} the super type declaration, or null if there is no super type. + * @return {ClassDeclaration} the super type declaration, or null if there is no super type. + * @deprecated */ - getSuperTypeDeclaration(): ScalarDeclaration | null; + getSuperTypeDeclaration(): ClassDeclaration; /** * Returns the validator string for this scalar definition * @return {Validator} the validator for the field or null */ getValidator(): Validator; /** - * Returns the default value for the field or null - * @return {string | number | null} the default value for the field or null - */ + * Returns the default value for the field or null + * @return {string | number | null} the default value for the field or null + */ getDefaultValue(): string | number | null; /** * Returns true if this class is abstract. * * @return {boolean} true if the class is abstract + * @deprecated */ isAbstract(): boolean; /** * Returns true if this class is the definition of an asset. * * @return {boolean} true if the class is an asset + * @deprecated */ isAsset(): boolean; /** * Returns true if this class is the definition of a participant. * * @return {boolean} true if the class is a participant + * @deprecated */ isParticipant(): boolean; /** * Returns true if this class is the definition of a transaction. * * @return {boolean} true if the class is a transaction + * @deprecated */ isTransaction(): boolean; /** * Returns true if this class is the definition of an event. * * @return {boolean} true if the class is an event + * @deprecated */ isEvent(): boolean; /** * Returns true if this class is the definition of a concept. * * @return {boolean} true if the class is a concept + * @deprecated */ isConcept(): boolean; - /** - * Returns true if this class is the definition of an enum. - * - * @return {boolean} true if the class is an enum - */ - isEnum(): boolean; - /** - * Returns true if this class is the definition of a class declaration. - * - * @return {boolean} true if the class is a class - */ - isClassDeclaration(): boolean; - /** - * Returns true if this class is the definition of a scalar declaration. - * - * @return {boolean} true if the class is a scalar - */ - isScalarDeclaration(): boolean; } -import Decorated = require("./decorated"); -import ModelFile = require("./modelfile"); +import Declaration = require("./declaration"); import StringValidator = require("./stringvalidator"); import NumberValidator = require("./numbervalidator"); +import ClassDeclaration = require("./classdeclaration"); import Validator = require("./validator"); diff --git a/packages/concerto-core/types/lib/modelutil.d.ts b/packages/concerto-core/types/lib/modelutil.d.ts index 413f11310f..6962584bc2 100644 --- a/packages/concerto-core/types/lib/modelutil.d.ts +++ b/packages/concerto-core/types/lib/modelutil.d.ts @@ -93,7 +93,7 @@ declare class ModelUtil { private static isEnum; /** * Returns the true if the given field is a Scalar type - * @param {Scalar} scalar - the string + * @param {Field} field - the Field to test * @return {boolean} true if the field is declared as an scalar * @private */ diff --git a/packages/concerto-tools/test/codegen/__snapshots__/codegen.js.snap b/packages/concerto-tools/test/codegen/__snapshots__/codegen.js.snap index 160f3a89b0..8297368b11 100644 --- a/packages/concerto-tools/test/codegen/__snapshots__/codegen.js.snap +++ b/packages/concerto-tools/test/codegen/__snapshots__/codegen.js.snap @@ -1988,6 +1988,7 @@ class \`org.acme.hr.Employee\` { + \`Department\` \`department\` + \`Address\` \`officeAddress\` + \`Equipment[]\` \`companyAssets\` +\`org.acme.hr.Employee\` "1" o-- "1" \`org.acme.hr.Manager\` : manager } \`org.acme.hr.Employee\` "1" *-- "1" \`org.acme.hr.Department\` @@ -1998,6 +1999,7 @@ class \`org.acme.hr.Employee\` { class \`org.acme.hr.Contractor\` { << participant>> + \`Company\` \`company\` +\`org.acme.hr.Contractor\` "1" o-- "1" \`org.acme.hr.Manager\` : manager } \`org.acme.hr.Contractor\` "1" *-- "1" \`org.acme.hr.Company\` @@ -2005,6 +2007,7 @@ class \`org.acme.hr.Contractor\` { \`org.acme.hr.Contractor\` --|> \`org.acme.hr.Person\` class \`org.acme.hr.Manager\` { << participant>> +\`org.acme.hr.Manager\` "1" o-- "*" \`org.acme.hr.Person\` : reports } \`org.acme.hr.Manager\` "1" o-- "*" \`org.acme.hr.Person\` : reports @@ -2014,12 +2017,14 @@ class \`org.acme.hr.CompanyEvent\` class \`org.acme.hr.Onboarded\` { << event>> +\`org.acme.hr.Onboarded\` "1" o-- "1" \`org.acme.hr.Employee\` : employee } \`org.acme.hr.Onboarded\` "1" o-- "1" \`org.acme.hr.Employee\` : employee \`org.acme.hr.Onboarded\` --|> \`org.acme.hr.CompanyEvent\` class \`org.acme.hr.ChangeOfAddress\` { << transaction>> +\`org.acme.hr.ChangeOfAddress\` "1" o-- "1" \`org.acme.hr.Person\` : Person + \`Address\` \`newAddress\` } @@ -2115,6 +2120,7 @@ class \`org.acme.hr.Employee\` { + \`Department\` \`department\` + \`Address\` \`officeAddress\` + \`Equipment[]\` \`companyAssets\` +\`org.acme.hr.Employee\` "1" o-- "1" \`org.acme.hr.Manager\` : manager } \`org.acme.hr.Employee\` "1" o-- "1" \`org.acme.hr.Manager\` : manager @@ -2122,12 +2128,14 @@ class \`org.acme.hr.Employee\` { class \`org.acme.hr.Contractor\` { << participant>> + \`Company\` \`company\` +\`org.acme.hr.Contractor\` "1" o-- "1" \`org.acme.hr.Manager\` : manager } \`org.acme.hr.Contractor\` "1" o-- "1" \`org.acme.hr.Manager\` : manager \`org.acme.hr.Contractor\` --|> \`org.acme.hr.Person\` class \`org.acme.hr.Manager\` { << participant>> +\`org.acme.hr.Manager\` "1" o-- "*" \`org.acme.hr.Person\` : reports } \`org.acme.hr.Manager\` "1" o-- "*" \`org.acme.hr.Person\` : reports @@ -2138,12 +2146,14 @@ class \`org.acme.hr.CompanyEvent\` \`org.acme.hr.CompanyEvent\` --|> \`concerto@1.0.0.Event\` class \`org.acme.hr.Onboarded\` { << event>> +\`org.acme.hr.Onboarded\` "1" o-- "1" \`org.acme.hr.Employee\` : employee } \`org.acme.hr.Onboarded\` "1" o-- "1" \`org.acme.hr.Employee\` : employee \`org.acme.hr.Onboarded\` --|> \`org.acme.hr.CompanyEvent\` class \`org.acme.hr.ChangeOfAddress\` { << transaction>> +\`org.acme.hr.ChangeOfAddress\` "1" o-- "1" \`org.acme.hr.Person\` : Person + \`Address\` \`newAddress\` } @@ -3184,15 +3194,18 @@ class org.acme.hr.Employee << (P,lightblue) >> { + Department department + Address officeAddress + Equipment[] companyAssets + + Manager manager } org.acme.hr.Employee "1" o-- "1" org.acme.hr.Manager : manager org.acme.hr.Employee --|> org.acme.hr.Person class org.acme.hr.Contractor << (P,lightblue) >> { + Company company + + Manager manager } org.acme.hr.Contractor "1" o-- "1" org.acme.hr.Manager : manager org.acme.hr.Contractor --|> org.acme.hr.Person class org.acme.hr.Manager << (P,lightblue) >> { + + Person[] reports } org.acme.hr.Manager "1" o-- "*" org.acme.hr.Person : reports org.acme.hr.Manager --|> org.acme.hr.Employee @@ -3200,10 +3213,12 @@ class org.acme.hr.CompanyEvent { } org.acme.hr.CompanyEvent --|> concerto_1.0.0.Event class org.acme.hr.Onboarded { + + Employee employee } org.acme.hr.Onboarded "1" o-- "1" org.acme.hr.Employee : employee org.acme.hr.Onboarded --|> org.acme.hr.CompanyEvent class org.acme.hr.ChangeOfAddress << (T,yellow) >> { + + Person Person + Address newAddress } org.acme.hr.ChangeOfAddress "1" o-- "1" org.acme.hr.Person : Person @@ -3948,6 +3963,7 @@ class \`org.acme.hr@1.0.0.Employee\` { + \`Department\` \`department\` + \`Address\` \`officeAddress\` + \`Equipment[]\` \`companyAssets\` +\`org.acme.hr@1.0.0.Employee\` "1" o-- "1" \`org.acme.hr@1.0.0.Manager\` : manager } \`org.acme.hr@1.0.0.Employee\` "1" *-- "1" \`org.acme.hr@1.0.0.Department\` @@ -3958,6 +3974,7 @@ class \`org.acme.hr@1.0.0.Employee\` { class \`org.acme.hr@1.0.0.Contractor\` { << participant>> + \`Company\` \`company\` +\`org.acme.hr@1.0.0.Contractor\` "1" o-- "1" \`org.acme.hr@1.0.0.Manager\` : manager } \`org.acme.hr@1.0.0.Contractor\` "1" *-- "1" \`org.acme.hr@1.0.0.Company\` @@ -3965,6 +3982,7 @@ class \`org.acme.hr@1.0.0.Contractor\` { \`org.acme.hr@1.0.0.Contractor\` --|> \`org.acme.hr@1.0.0.Person\` class \`org.acme.hr@1.0.0.Manager\` { << participant>> +\`org.acme.hr@1.0.0.Manager\` "1" o-- "*" \`org.acme.hr@1.0.0.Person\` : reports } \`org.acme.hr@1.0.0.Manager\` "1" o-- "*" \`org.acme.hr@1.0.0.Person\` : reports @@ -3974,12 +3992,14 @@ class \`org.acme.hr@1.0.0.CompanyEvent\` class \`org.acme.hr@1.0.0.Onboarded\` { << event>> +\`org.acme.hr@1.0.0.Onboarded\` "1" o-- "1" \`org.acme.hr@1.0.0.Employee\` : employee } \`org.acme.hr@1.0.0.Onboarded\` "1" o-- "1" \`org.acme.hr@1.0.0.Employee\` : employee \`org.acme.hr@1.0.0.Onboarded\` --|> \`org.acme.hr@1.0.0.CompanyEvent\` class \`org.acme.hr@1.0.0.ChangeOfAddress\` { << transaction>> +\`org.acme.hr@1.0.0.ChangeOfAddress\` "1" o-- "1" \`org.acme.hr@1.0.0.Person\` : Person + \`Address\` \`newAddress\` } @@ -4057,6 +4077,7 @@ class org.acme.hr_1.0.0.Employee << (P,lightblue) >> { + Department department + Address officeAddress + Equipment[] companyAssets + + Manager manager } org.acme.hr@1.0.0.Employee "1" *-- "1" org.acme.hr@1.0.0.Department : department org.acme.hr@1.0.0.Employee "1" *-- "1" org.acme.hr@1.0.0.Address : officeAddress @@ -4065,21 +4086,25 @@ org.acme.hr@1.0.0.Employee "1" o-- "1" org.acme.hr@1.0.0.Manager : manager org.acme.hr_1.0.0.Employee --|> org.acme.hr_1.0.0.Person class org.acme.hr_1.0.0.Contractor << (P,lightblue) >> { + Company company + + Manager manager } org.acme.hr@1.0.0.Contractor "1" *-- "1" org.acme.hr@1.0.0.Company : company org.acme.hr@1.0.0.Contractor "1" o-- "1" org.acme.hr@1.0.0.Manager : manager org.acme.hr_1.0.0.Contractor --|> org.acme.hr_1.0.0.Person class org.acme.hr_1.0.0.Manager << (P,lightblue) >> { + + Person[] reports } org.acme.hr@1.0.0.Manager "1" o-- "*" org.acme.hr@1.0.0.Person : reports org.acme.hr_1.0.0.Manager --|> org.acme.hr_1.0.0.Employee class org.acme.hr_1.0.0.CompanyEvent { } class org.acme.hr_1.0.0.Onboarded { + + Employee employee } org.acme.hr@1.0.0.Onboarded "1" o-- "1" org.acme.hr@1.0.0.Employee : employee org.acme.hr_1.0.0.Onboarded --|> org.acme.hr_1.0.0.CompanyEvent class org.acme.hr_1.0.0.ChangeOfAddress << (T,yellow) >> { + + Person Person + Address newAddress } org.acme.hr@1.0.0.ChangeOfAddress "1" o-- "1" org.acme.hr@1.0.0.Person : Person @@ -6077,6 +6102,7 @@ class \`org.acme.hr@1.0.0.Employee\` { + \`Department\` \`department\` + \`Address\` \`officeAddress\` + \`Equipment[]\` \`companyAssets\` +\`org.acme.hr@1.0.0.Employee\` "1" o-- "1" \`org.acme.hr@1.0.0.Manager\` : manager } \`org.acme.hr@1.0.0.Employee\` "1" *-- "1" \`org.acme.hr@1.0.0.Department\` @@ -6087,6 +6113,7 @@ class \`org.acme.hr@1.0.0.Employee\` { class \`org.acme.hr@1.0.0.Contractor\` { << participant>> + \`Company\` \`company\` +\`org.acme.hr@1.0.0.Contractor\` "1" o-- "1" \`org.acme.hr@1.0.0.Manager\` : manager } \`org.acme.hr@1.0.0.Contractor\` "1" *-- "1" \`org.acme.hr@1.0.0.Company\` @@ -6094,6 +6121,7 @@ class \`org.acme.hr@1.0.0.Contractor\` { \`org.acme.hr@1.0.0.Contractor\` --|> \`org.acme.hr@1.0.0.Person\` class \`org.acme.hr@1.0.0.Manager\` { << participant>> +\`org.acme.hr@1.0.0.Manager\` "1" o-- "*" \`org.acme.hr@1.0.0.Person\` : reports } \`org.acme.hr@1.0.0.Manager\` "1" o-- "*" \`org.acme.hr@1.0.0.Person\` : reports @@ -6103,12 +6131,14 @@ class \`org.acme.hr@1.0.0.CompanyEvent\` class \`org.acme.hr@1.0.0.Onboarded\` { << event>> +\`org.acme.hr@1.0.0.Onboarded\` "1" o-- "1" \`org.acme.hr@1.0.0.Employee\` : employee } \`org.acme.hr@1.0.0.Onboarded\` "1" o-- "1" \`org.acme.hr@1.0.0.Employee\` : employee \`org.acme.hr@1.0.0.Onboarded\` --|> \`org.acme.hr@1.0.0.CompanyEvent\` class \`org.acme.hr@1.0.0.ChangeOfAddress\` { << transaction>> +\`org.acme.hr@1.0.0.ChangeOfAddress\` "1" o-- "1" \`org.acme.hr@1.0.0.Person\` : Person + \`Address\` \`newAddress\` } @@ -6209,6 +6239,7 @@ class \`org.acme.hr@1.0.0.Employee\` { + \`Department\` \`department\` + \`Address\` \`officeAddress\` + \`Equipment[]\` \`companyAssets\` +\`org.acme.hr@1.0.0.Employee\` "1" o-- "1" \`org.acme.hr@1.0.0.Manager\` : manager } \`org.acme.hr@1.0.0.Employee\` "1" *-- "1" \`org.acme.hr@1.0.0.Department\` @@ -6219,6 +6250,7 @@ class \`org.acme.hr@1.0.0.Employee\` { class \`org.acme.hr@1.0.0.Contractor\` { << participant>> + \`Company\` \`company\` +\`org.acme.hr@1.0.0.Contractor\` "1" o-- "1" \`org.acme.hr@1.0.0.Manager\` : manager } \`org.acme.hr@1.0.0.Contractor\` "1" *-- "1" \`org.acme.hr@1.0.0.Company\` @@ -6226,6 +6258,7 @@ class \`org.acme.hr@1.0.0.Contractor\` { \`org.acme.hr@1.0.0.Contractor\` --|> \`org.acme.hr@1.0.0.Person\` class \`org.acme.hr@1.0.0.Manager\` { << participant>> +\`org.acme.hr@1.0.0.Manager\` "1" o-- "*" \`org.acme.hr@1.0.0.Person\` : reports } \`org.acme.hr@1.0.0.Manager\` "1" o-- "*" \`org.acme.hr@1.0.0.Person\` : reports @@ -6236,12 +6269,14 @@ class \`org.acme.hr@1.0.0.CompanyEvent\` \`org.acme.hr@1.0.0.CompanyEvent\` --|> \`concerto@1.0.0.Event\` class \`org.acme.hr@1.0.0.Onboarded\` { << event>> +\`org.acme.hr@1.0.0.Onboarded\` "1" o-- "1" \`org.acme.hr@1.0.0.Employee\` : employee } \`org.acme.hr@1.0.0.Onboarded\` "1" o-- "1" \`org.acme.hr@1.0.0.Employee\` : employee \`org.acme.hr@1.0.0.Onboarded\` --|> \`org.acme.hr@1.0.0.CompanyEvent\` class \`org.acme.hr@1.0.0.ChangeOfAddress\` { << transaction>> +\`org.acme.hr@1.0.0.ChangeOfAddress\` "1" o-- "1" \`org.acme.hr@1.0.0.Person\` : Person + \`Address\` \`newAddress\` } @@ -7288,6 +7323,7 @@ class org.acme.hr_1.0.0.Employee << (P,lightblue) >> { + Department department + Address officeAddress + Equipment[] companyAssets + + Manager manager } org.acme.hr@1.0.0.Employee "1" *-- "1" org.acme.hr@1.0.0.Department : department org.acme.hr@1.0.0.Employee "1" *-- "1" org.acme.hr@1.0.0.Address : officeAddress @@ -7296,11 +7332,13 @@ org.acme.hr@1.0.0.Employee "1" o-- "1" org.acme.hr@1.0.0.Manager : manager org.acme.hr_1.0.0.Employee --|> org.acme.hr_1.0.0.Person class org.acme.hr_1.0.0.Contractor << (P,lightblue) >> { + Company company + + Manager manager } org.acme.hr@1.0.0.Contractor "1" *-- "1" org.acme.hr@1.0.0.Company : company org.acme.hr@1.0.0.Contractor "1" o-- "1" org.acme.hr@1.0.0.Manager : manager org.acme.hr_1.0.0.Contractor --|> org.acme.hr_1.0.0.Person class org.acme.hr_1.0.0.Manager << (P,lightblue) >> { + + Person[] reports } org.acme.hr@1.0.0.Manager "1" o-- "*" org.acme.hr@1.0.0.Person : reports org.acme.hr_1.0.0.Manager --|> org.acme.hr_1.0.0.Employee @@ -7308,10 +7346,12 @@ class org.acme.hr_1.0.0.CompanyEvent { } org.acme.hr_1.0.0.CompanyEvent --|> concerto_1.0.0.Event class org.acme.hr_1.0.0.Onboarded { + + Employee employee } org.acme.hr@1.0.0.Onboarded "1" o-- "1" org.acme.hr@1.0.0.Employee : employee org.acme.hr_1.0.0.Onboarded --|> org.acme.hr_1.0.0.CompanyEvent class org.acme.hr_1.0.0.ChangeOfAddress << (T,yellow) >> { + + Person Person + Address newAddress } org.acme.hr@1.0.0.ChangeOfAddress "1" o-- "1" org.acme.hr@1.0.0.Person : Person From 637bd761c0700feef8e47824192cecf0b5fe462c Mon Sep 17 00:00:00 2001 From: Matt Roberts Date: Tue, 7 Mar 2023 12:34:57 +0000 Subject: [PATCH 5/6] test(code): fix tests Signed-off-by: Matt Roberts --- packages/concerto-core/api.txt | 40 +++++----- packages/concerto-core/changelog.txt | 3 + packages/concerto-core/index.js | 2 +- .../concerto-core/lib/basemodelmanager.js | 12 ++- .../concerto-core/lib/introspect/modelfile.js | 10 ++- .../test/introspect/declaration.js | 73 +++++++++++++++++++ .../test/introspect/decorator.js | 2 + packages/concerto-core/types/index.d.ts | 2 +- .../types/lib/basemodelmanager.d.ts | 12 ++- .../types/lib/introspect/modelfile.d.ts | 8 +- packages/concerto-tools/lib/common/common.js | 2 +- packages/concerto-tools/test/common/graph.js | 4 +- .../types/lib/common/common.d.ts | 2 +- 13 files changed, 142 insertions(+), 30 deletions(-) create mode 100644 packages/concerto-core/test/introspect/declaration.js diff --git a/packages/concerto-core/api.txt b/packages/concerto-core/api.txt index 715d1d79d1..76572833e3 100644 --- a/packages/concerto-core/api.txt +++ b/packages/concerto-core/api.txt @@ -34,6 +34,7 @@ class BaseModelManager { + object resolveMetaModel(object) + void fromAst(ast) + void getAst(boolean?) + + BaseModelManager filter(FilterFunction) } class Concerto { + void constructor(ModelManager) @@ -71,15 +72,10 @@ class AssetDeclaration extends IdentifiedDeclaration { + void constructor(ModelFile,Object) throws IllegalModelException + string declarationKind() } -class ClassDeclaration extends Decorated { - + void constructor(ModelFile,Object) throws IllegalModelException - + ModelFile getModelFile() +class ClassDeclaration extends Declaration { + ClassDeclaration _resolveSuperType() ~ void validate() throws IllegalModelException + boolean isAbstract() - + string getName() - + string getNamespace() - + string getFullyQualifiedName() + Boolean isIdentified() + Boolean isSystemIdentified() + Boolean isExplicitlyIdentified() @@ -107,6 +103,21 @@ class ConceptDeclaration extends ClassDeclaration { + void constructor(ModelFile,Object) throws IllegalModelException + string declarationKind() } +class Declaration extends Decorated { + + void constructor(ModelFile,Object) throws IllegalModelException + + ModelFile getModelFile() + + string getName() + + string getNamespace() + + string getFullyQualifiedName() + + Boolean isIdentified() + + Boolean isSystemIdentified() + + string getIdentifierFieldName() + + string getType() + + String toString() + + boolean isEnum() + + boolean isClassDeclaration() + + boolean isScalarDeclaration() +} class Decorator { + void constructor(ClassDeclaration|Property,Object) throws IllegalModelException + void getParent() @@ -202,31 +213,24 @@ class RelationshipDeclaration extends Property { + String toString() + boolean isRelationship() } -class ScalarDeclaration extends Decorated { - + void constructor(ModelFile,Object) throws IllegalModelException - + ModelFile getModelFile() +class ScalarDeclaration extends Declaration { ~ void validate() throws IllegalModelException - + string getName() - + string getNamespace() - + string getFullyQualifiedName() + Boolean isIdentified() + Boolean isSystemIdentified() - + void getIdentifierFieldName() + + string getIdentifierFieldName() + string getType() - + void getSuperType() - + void getSuperTypeDeclaration() + + string getSuperType() + + ClassDeclaration getSuperTypeDeclaration() + Validator getValidator() + void getDefaultValue() + String toString() + boolean isAbstract() + + boolean isScalarDeclaration() + boolean isAsset() + boolean isParticipant() + boolean isTransaction() + boolean isEvent() + boolean isConcept() - + boolean isEnum() - + boolean isClassDeclaration() - + boolean isScalarDeclaration() } class TransactionDeclaration extends IdentifiedDeclaration { + void constructor(ModelFile,Object) throws IllegalModelException diff --git a/packages/concerto-core/changelog.txt b/packages/concerto-core/changelog.txt index 31c9f0bb02..7113379924 100644 --- a/packages/concerto-core/changelog.txt +++ b/packages/concerto-core/changelog.txt @@ -24,6 +24,9 @@ # Note that the latest public API is documented using JSDocs and is available in api.txt. # +Version 3.5.0 {b419dc1b3db9662d8c941a044cd8db5a} 2023-03-07 +- Introduce Declaration type. ClassDeclaration and Scalar Declaration both extend Declaration. + Version 3.0.0 {5fac664420fea3649a7a304941f190f1} 2022-08-28 - Allow client-provided RegExp engine to ModelManager - Allow decorators to be attached to model files/namespaces diff --git a/packages/concerto-core/index.js b/packages/concerto-core/index.js index 5dea28f854..b1d1436016 100644 --- a/packages/concerto-core/index.js +++ b/packages/concerto-core/index.js @@ -39,7 +39,7 @@ const EnumValueDeclaration = require('./lib/introspect/enumvaluedeclaration'); const EventDeclaration = require('./lib/introspect/eventdeclaration'); const ParticipantDeclaration = require('./lib/introspect/participantdeclaration'); const TransactionDeclaration = require('./lib/introspect/transactiondeclaration'); -const ScalarDeclaration = require('./lib/introspect/scalarDeclaration'); +const ScalarDeclaration = require('./lib/introspect/scalardeclaration'); // Properties const Property = require('./lib/introspect/property'); diff --git a/packages/concerto-core/lib/basemodelmanager.js b/packages/concerto-core/lib/basemodelmanager.js index 8c1152f9f7..02cd6cf3fa 100644 --- a/packages/concerto-core/lib/basemodelmanager.js +++ b/packages/concerto-core/lib/basemodelmanager.js @@ -32,7 +32,7 @@ const { getRootModel } = require('./rootmodel'); /* eslint-disable no-unused-vars */ /* istanbul ignore next */ if (global === undefined) { - const Decorated = require('./introspect/decorated'); + const Declaration = require('./introspect/declaration'); const AssetDeclaration = require('./introspect/assetdeclaration'); const ClassDeclaration = require('./introspect/classdeclaration'); const ConceptDeclaration = require('./introspect/conceptdeclaration'); @@ -711,13 +711,21 @@ class BaseModelManager { return result; } + + /** + * A function type definition for use as an argument to the filter function + * @callback FilterFunction + * @param {Declaration} declaration + * @returns {boolean} true, if the declaration satisfies the filter function + */ + /** * Returns a new ModelManager with only the types for which the * filter function returns true. * * ModelFiles with no declarations after filtering will be removed. * - * @param {function(Decorated): boolean} predicate - the filter function over a Decorated object + * @param {FilterFunction} predicate - the filter function over a Declaration object * @returns {BaseModelManager} - the filtered ModelManager */ filter(predicate){ diff --git a/packages/concerto-core/lib/introspect/modelfile.js b/packages/concerto-core/lib/introspect/modelfile.js index cea2cba247..f776e99b56 100644 --- a/packages/concerto-core/lib/introspect/modelfile.js +++ b/packages/concerto-core/lib/introspect/modelfile.js @@ -36,6 +36,7 @@ const Decorated = require('./decorated'); if (global === undefined) { const ClassDeclaration = require('./classdeclaration'); const ModelManager = require('../modelmanager'); + const Declaration = require('./declaration'); } /* eslint-enable no-unused-vars */ @@ -802,13 +803,20 @@ class ModelFile extends Decorated { } } + /** + * A function type definition for use as an argument to the filter function + * @callback FilterFunction + * @param {Declaration} declaration + * @returns {boolean} true, if the declaration satisfies the filter function + */ + /** * Returns a new ModelFile with only the types for which the * filter function returns true. * * Will return null if the filtered ModelFile doesn't contain any declarations. * - * @param {function(Decorated): boolean} predicate - the filter function over a Decorated object + * @param {FilterFunction} predicate - the filter function over a Declaration object * @param {ModelManager} modelManager - the target ModelManager for the filtered ModelFile * @returns {ModelFile?} - the filtered ModelFile * @private diff --git a/packages/concerto-core/test/introspect/declaration.js b/packages/concerto-core/test/introspect/declaration.js new file mode 100644 index 0000000000..cd262b54a0 --- /dev/null +++ b/packages/concerto-core/test/introspect/declaration.js @@ -0,0 +1,73 @@ +/* + * 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 Declaration = require('../../lib/introspect/declaration'); +const ModelFile = require('../../lib/introspect/modelfile'); + +require('chai').should(); +const should = require('chai').should(); +const sinon = require('sinon'); + +describe('Decorated', () => { + + let modelFile; + let decorated; + + beforeEach(() => { + modelFile = sinon.createStubInstance(ModelFile); + decorated = new Declaration(modelFile, { ast: true }); + }); + + describe('#constructor', () => { + + it('should throw if ast not specified', () => { + (() => { + new Declaration(null); + }).should.throw(/ast not specified/); + }); + + }); + + describe('#isIdentified', () => { + it('should be false', () => { + decorated.isIdentified().should.equal(false); + }); + }); + + describe('#isSystemIdentified', () => { + it('should be false', () => { + decorated.isSystemIdentified().should.equal(false); + }); + }); + + describe('#getIdentifierFieldName', () => { + it('should be null', () => { + should.equal(decorated.getIdentifierFieldName(), null); + }); + }); + + describe('#getType', () => { + it('should be null', () => { + should.equal(decorated.getType(), null); + }); + }); + + describe('#toString', () => { + it('should be null', () => { + should.equal(decorated.toString(), null); + }); + }); +}); diff --git a/packages/concerto-core/test/introspect/decorator.js b/packages/concerto-core/test/introspect/decorator.js index f9ef69025e..1828cbe726 100644 --- a/packages/concerto-core/test/introspect/decorator.js +++ b/packages/concerto-core/test/introspect/decorator.js @@ -57,6 +57,8 @@ describe('Decorator', () => { d.getParent().should.equal(mockAssetDeclaration); d.getName().should.equal('Test'); d.getArguments().should.deep.equal(['one','two','three']); + d.isDecorator().should.equal(true); + }); }); diff --git a/packages/concerto-core/types/index.d.ts b/packages/concerto-core/types/index.d.ts index 7cb8f1652c..66b9acb866 100644 --- a/packages/concerto-core/types/index.d.ts +++ b/packages/concerto-core/types/index.d.ts @@ -12,7 +12,7 @@ import EnumValueDeclaration = require("./lib/introspect/enumvaluedeclaration"); import EventDeclaration = require("./lib/introspect/eventdeclaration"); import ParticipantDeclaration = require("./lib/introspect/participantdeclaration"); import TransactionDeclaration = require("./lib/introspect/transactiondeclaration"); -import ScalarDeclaration = require("./lib/introspect/scalarDeclaration"); +import ScalarDeclaration = require("./lib/introspect/scalardeclaration"); import Property = require("./lib/introspect/property"); import Field = require("./lib/introspect/field"); import EnumDeclaration = require("./lib/introspect/enumdeclaration"); diff --git a/packages/concerto-core/types/lib/basemodelmanager.d.ts b/packages/concerto-core/types/lib/basemodelmanager.d.ts index 5bae265fbd..94c3802071 100644 --- a/packages/concerto-core/types/lib/basemodelmanager.d.ts +++ b/packages/concerto-core/types/lib/basemodelmanager.d.ts @@ -295,16 +295,22 @@ declare class BaseModelManager { * @returns {*} the metamodel */ getAst(resolve?: boolean): any; + /** + * A function type definition for use as an argument to the filter function + * @callback FilterFunction + * @param {Declaration} declaration + * @returns {boolean} true, if the declaration satisfies the filter function + */ /** * Returns a new ModelManager with only the types for which the * filter function returns true. * * ModelFiles with no declarations after filtering will be removed. * - * @param {function(Decorated): boolean} predicate - the filter function over a Decorated object + * @param {FilterFunction} predicate - the filter function over a Declaration object * @returns {BaseModelManager} - the filtered ModelManager */ - filter(predicate: (arg0: Decorated) => boolean): BaseModelManager; + filter(predicate: (declaration: Declaration) => boolean): BaseModelManager; } import ModelFile = require("./introspect/modelfile"); import { FileDownloader } from "@accordproject/concerto-util"; @@ -318,4 +324,4 @@ import ConceptDeclaration = require("./introspect/conceptdeclaration"); import Factory = require("./factory"); import Serializer = require("./serializer"); import DecoratorFactory = require("./introspect/decoratorfactory"); -import Decorated = require("./introspect/decorated"); +import Declaration = require("./introspect/declaration"); diff --git a/packages/concerto-core/types/lib/introspect/modelfile.d.ts b/packages/concerto-core/types/lib/introspect/modelfile.d.ts index 8b9bc5b207..9a57c3a10c 100644 --- a/packages/concerto-core/types/lib/introspect/modelfile.d.ts +++ b/packages/concerto-core/types/lib/introspect/modelfile.d.ts @@ -259,13 +259,19 @@ declare class ModelFile extends Decorated { */ private fromAst; namespace: any; + /** + * A function type definition for use as an argument to the filter function + * @callback FilterFunction + * @param {Declaration} declaration + * @returns {boolean} true, if the declaration satisfies the filter function + */ /** * Returns a new ModelFile with only the types for which the * filter function returns true. * * Will return null if the filtered ModelFile doesn't contain any declarations. * - * @param {function(Decorated): boolean} predicate - the filter function over a Decorated object + * @param {FilterFunction} predicate - the filter function over a Declaration object * @param {ModelManager} modelManager - the target ModelManager for the filtered ModelFile * @returns {ModelFile?} - the filtered ModelFile * @private diff --git a/packages/concerto-tools/lib/common/common.js b/packages/concerto-tools/lib/common/common.js index b0d1132a5c..f5414bea1c 100644 --- a/packages/concerto-tools/lib/common/common.js +++ b/packages/concerto-tools/lib/common/common.js @@ -14,7 +14,7 @@ 'use strict'; -const DiagramVisitor = require('./diagramVisitor'); +const DiagramVisitor = require('./diagramvisitor'); const { ConcertoGraphVisitor, DirectedGraph } = require('./graph'); diff --git a/packages/concerto-tools/test/common/graph.js b/packages/concerto-tools/test/common/graph.js index 603e1a5ed5..31837c461a 100644 --- a/packages/concerto-tools/test/common/graph.js +++ b/packages/concerto-tools/test/common/graph.js @@ -14,7 +14,7 @@ 'use strict'; -const { DirectedGraph, ConcertoGraphVisitor } = require('../../lib/common/graph.js'); +const { DirectedGraph, ConcertoGraphVisitor } = require('../../lib/common/common.js'); const { ModelManager } = require('@accordproject/concerto-core'); const fs = require('fs'); const { expect } = require('expect'); @@ -102,7 +102,9 @@ describe('graph', function () { const graph = new DirectedGraph(); modelManager.accept(visitor, { graph }); + const connectedGraph = graph.findConnectedGraph('org.acme.hr@1.0.0.ChangeOfAddress'); + expect(connectedGraph.hasEdge('org.acme.hr@1.0.0.ChangeOfAddress', 'org.acme.hr@1.0.0.Person')); const filteredModelManager = modelManager.filter(declaration => connectedGraph.hasVertex(declaration.getFullyQualifiedName())); diff --git a/packages/concerto-tools/types/lib/common/common.d.ts b/packages/concerto-tools/types/lib/common/common.d.ts index d95dfa41b5..56cfd3f67d 100644 --- a/packages/concerto-tools/types/lib/common/common.d.ts +++ b/packages/concerto-tools/types/lib/common/common.d.ts @@ -1,4 +1,4 @@ -import DiagramVisitor = require("./diagramVisitor"); +import DiagramVisitor = require("./diagramvisitor"); import { ConcertoGraphVisitor } from "./graph"; import { DirectedGraph } from "./graph"; export { DiagramVisitor, ConcertoGraphVisitor, DirectedGraph }; From c6f1560563c2730ccbe2724bc31a97699fc8626d Mon Sep 17 00:00:00 2001 From: Matt Roberts Date: Wed, 8 Mar 2023 13:59:56 +0000 Subject: [PATCH 6/6] test(core): fix copy pasta Signed-off-by: Matt Roberts --- .../concerto-core/test/introspect/declaration.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/concerto-core/test/introspect/declaration.js b/packages/concerto-core/test/introspect/declaration.js index cd262b54a0..90c5f1f256 100644 --- a/packages/concerto-core/test/introspect/declaration.js +++ b/packages/concerto-core/test/introspect/declaration.js @@ -21,14 +21,14 @@ require('chai').should(); const should = require('chai').should(); const sinon = require('sinon'); -describe('Decorated', () => { +describe('Declaration', () => { let modelFile; - let decorated; + let declaration; beforeEach(() => { modelFile = sinon.createStubInstance(ModelFile); - decorated = new Declaration(modelFile, { ast: true }); + declaration = new Declaration(modelFile, { ast: true }); }); describe('#constructor', () => { @@ -43,31 +43,31 @@ describe('Decorated', () => { describe('#isIdentified', () => { it('should be false', () => { - decorated.isIdentified().should.equal(false); + declaration.isIdentified().should.equal(false); }); }); describe('#isSystemIdentified', () => { it('should be false', () => { - decorated.isSystemIdentified().should.equal(false); + declaration.isSystemIdentified().should.equal(false); }); }); describe('#getIdentifierFieldName', () => { it('should be null', () => { - should.equal(decorated.getIdentifierFieldName(), null); + should.equal(declaration.getIdentifierFieldName(), null); }); }); describe('#getType', () => { it('should be null', () => { - should.equal(decorated.getType(), null); + should.equal(declaration.getType(), null); }); }); describe('#toString', () => { it('should be null', () => { - should.equal(decorated.toString(), null); + should.equal(declaration.toString(), null); }); }); });