From 2a412800761251b133293f7f691a87ddc724428a Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Thu, 5 Dec 2019 12:55:46 -0800 Subject: [PATCH 1/6] Add support for Generic class definitions Added support in parser to translate characters surrounded by `~` into generic type definition ie: `Class01~T~` would turn into `Class01` --- .../rendering/classDiagram.spec.js | 41 ++++++++++++++++++ dist/index.html | 37 ++++++++++++++++ src/diagrams/class/classDb.js | 32 +++++++++++--- src/diagrams/class/classDiagram.spec.js | 42 +++++++++++++++++++ src/diagrams/class/classRenderer.js | 8 +++- src/diagrams/class/parser/classDiagram.jison | 9 +++- 6 files changed, 161 insertions(+), 8 deletions(-) diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js index 29c4aba86..3247a4541 100644 --- a/cypress/integration/rendering/classDiagram.spec.js +++ b/cypress/integration/rendering/classDiagram.spec.js @@ -163,4 +163,45 @@ describe('Class diagram', () => { ); cy.get('svg'); }); + + it('5: should render a simple class diagram with Generic class', () => { + imgSnapshotTest( + ` + classDiagram + class Class01~T~ + Class01 : size() + Class01 : int chimp + Class01 : int gorilla + Class08 <--> C2: Cool label + class Class10~T~ { + <<service>> + int id + test() + } + `, + {} + ); + cy.get('svg'); + }); + + it('6: should render a simple class diagram with Generic class and relations', () => { + imgSnapshotTest( + ` + classDiagram + Class01~T~ <|-- AveryLongClass : Cool + Class03~T~ *-- Class04~T~ + Class01 : size() + Class01 : int chimp + Class01 : int gorilla + Class08 <--> C2: Cool label + class Class10~T~ { + <<service>> + int id + test() + } + `, + {} + ); + cy.get('svg'); + }); }); diff --git a/dist/index.html b/dist/index.html index 2fb6f2ab6..3da07dd72 100644 --- a/dist/index.html +++ b/dist/index.html @@ -418,6 +418,43 @@ class Class10 { size() } + +
+ classDiagram + class Class01~T~ + Class01 : #size() + Class01 : -int chimp + Class01 : +int gorilla + class Class10~T~ { + <<service>> + int id + size() + } +
+ +
+ classDiagram + Class01~T~ <|-- AveryLongClass : Cool + <<interface>> Class01 + Class03~T~ "0" *-- "0..n" Class04 + Class05 "1" o-- "many" Class06 + Class07~T~ .. Class08 + Class09 "many" --> "1" C2 : Where am i? + Class09 "0" --* "1..n" C3 + Class09 --|> Class07 + Class07 : equals() + Class07 : Object[] elementData + Class01 : #size() + Class01 : -int chimp + Class01 : +int gorilla + Class08 <--> C2: Cool label + class Class10 { + <<service>> + int id + size() + } +
+
stateDiagram State1 diff --git a/src/diagrams/class/classDb.js b/src/diagrams/class/classDb.js index 6cfbcf3f4..8a6624af6 100644 --- a/src/diagrams/class/classDb.js +++ b/src/diagrams/class/classDb.js @@ -3,17 +3,32 @@ import { logger } from '../../logger'; let relations = []; let classes = {}; +const splitClassNameAndType = function(id){ + let genericType = ''; + let className = id; + + if(id.indexOf('~') > 0){ + let split = id.split('~'); + className = split[0]; + genericType = split[1]; + } + + return {className: className, type: genericType}; +} + /** * Function called by parser when a node definition has been found. * @param id * @public */ export const addClass = function(id) { + let classId = splitClassNameAndType(id); // Only add class if not exists - if (typeof classes[id] !== 'undefined') return; + if (typeof classes[classId.className] !== 'undefined') return; - classes[id] = { - id: id, + classes[classId.className] = { + id: classId.className, + type: classId.type, methods: [], members: [], annotations: [] @@ -40,6 +55,10 @@ export const addRelation = function(relation) { logger.debug('Adding relation: ' + JSON.stringify(relation)); addClass(relation.id1); addClass(relation.id2); + + relation.id1 = splitClassNameAndType(relation.id1).className; + relation.id2 = splitClassNameAndType(relation.id2).className; + relations.push(relation); }; @@ -51,7 +70,8 @@ export const addRelation = function(relation) { * @public */ export const addAnnotation = function(className, annotation) { - classes[className].annotations.push(annotation); + const validatedClassName = splitClassNameAndType(className).className; + classes[validatedClassName].annotations.push(annotation); }; /** @@ -64,7 +84,9 @@ export const addAnnotation = function(className, annotation) { * @public */ export const addMember = function(className, member) { - const theClass = classes[className]; + const validatedClassName = splitClassNameAndType(className).className; + const theClass = classes[validatedClassName]; + if (typeof member === 'string') { // Member can contain white spaces, we trim them out const memberString = member.trim(); diff --git a/src/diagrams/class/classDiagram.spec.js b/src/diagrams/class/classDiagram.spec.js index a1089d3d7..1d13f5158 100644 --- a/src/diagrams/class/classDiagram.spec.js +++ b/src/diagrams/class/classDiagram.spec.js @@ -56,6 +56,33 @@ describe('class diagram, ', function () { parser.parse(str); }); + it('should handle generic class', function() { + const str = + 'classDiagram\n' + + 'class Car~T~\n' + + 'Driver -- Car : drives >\n' + + 'Car *-- Wheel : have 4 >\n' + + 'Car -- Person : < owns'; + + parser.parse(str); + }); + + it('should handle generic class with brackets', function() { + const str = + 'classDiagram\n' + + 'class Dummy_Class~T~ {\n' + + 'String data\n' + + ' void methods()\n' + + '}\n' + + '\n' + + 'class Flight {\n' + + ' flightNumber : Integer\n' + + ' departureTime : Date\n' + + '}'; + + parser.parse(str); + }); + it('should handle class definitions', function() { const str = 'classDiagram\n' + @@ -326,6 +353,21 @@ describe('class diagram, ', function () { expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE); }); + it('should handle generic class with relation definitions', function () { + const str = 'classDiagram\n' + 'Class01~T~ <|-- Class02'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class01').id).toBe('Class01'); + expect(parser.yy.getClass('Class01').genericType).toBe('T'); + expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); + expect(relations[0].relation.type2).toBe('none'); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + }); + it('should handle class annotations', function () { const str = 'classDiagram\n' + 'class Class1\n' + '<> Class1'; parser.parse(str); diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index 9814237c4..aacb53569 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -318,10 +318,16 @@ const drawClass = function(elem, classDef) { isFirst = false; }); + let classTitleString = classDef.id; + + if(classDef.genericType !== undefined && classDef.genericType !== ''){ + classTitleString += '<' + classDef.genericType + '>'; + } + // add class title const classTitle = title .append('tspan') - .text(classDef.id) + .text(classTitleString) .attr('class', 'title'); // If class has annotations the title needs to have an offset of the text height diff --git a/src/diagrams/class/parser/classDiagram.jison b/src/diagrams/class/parser/classDiagram.jison index f34da91a5..1c1b8e669 100644 --- a/src/diagrams/class/parser/classDiagram.jison +++ b/src/diagrams/class/parser/classDiagram.jison @@ -6,7 +6,7 @@ /* lexical grammar */ %lex -%x string struct +%x string generic struct %% \%\%[^\n]*\n* /* do nothing */ @@ -23,6 +23,9 @@ "class" return 'CLASS'; "<<" return 'ANNOTATION_START'; ">>" return 'ANNOTATION_END'; +[~] this.begin("generic"); +[~] this.popState(); +[^~]* return "GENERICTYPE"; ["] this.begin("string"); ["] this.popState(); [^"]* return "STR"; @@ -36,7 +39,7 @@ \s*o return 'AGGREGATION'; \-\- return 'LINE'; \.\. return 'DOTTED_LINE'; -":"[^\n;]+ return 'LABEL'; +":"[^\n;]+ return 'LABEL'; \- return 'MINUS'; "." return 'DOT'; \+ return 'PLUS'; @@ -136,6 +139,8 @@ statements className : alphaNumToken className { $$=$1+$2; } | alphaNumToken { $$=$1; } + | alphaNumToken GENERICTYPE className { $$=$1+'~'+$2+$3; } + | alphaNumToken GENERICTYPE { $$=$1+'~'+$2; } ; statement From 6a9b251be15924e4893db253ffe810b90591b18e Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Fri, 6 Dec 2019 20:35:22 -0800 Subject: [PATCH 2/6] Fix code style errors --- src/diagrams/class/classDb.js | 8 ++++---- src/diagrams/class/classRenderer.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/diagrams/class/classDb.js b/src/diagrams/class/classDb.js index 8a6624af6..616dba4e6 100644 --- a/src/diagrams/class/classDb.js +++ b/src/diagrams/class/classDb.js @@ -3,18 +3,18 @@ import { logger } from '../../logger'; let relations = []; let classes = {}; -const splitClassNameAndType = function(id){ +const splitClassNameAndType = function(id) { let genericType = ''; let className = id; - if(id.indexOf('~') > 0){ + if(id.indexOf('~') > 0) { let split = id.split('~'); className = split[0]; genericType = split[1]; } - return {className: className, type: genericType}; -} + return { className: className, type: genericType }; +}; /** * Function called by parser when a node definition has been found. diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index aacb53569..67f78aa5b 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -320,7 +320,7 @@ const drawClass = function(elem, classDef) { let classTitleString = classDef.id; - if(classDef.genericType !== undefined && classDef.genericType !== ''){ + if (classDef.genericType !== undefined && classDef.genericType !== '') { classTitleString += '<' + classDef.genericType + '>'; } From 2eaa7f1ab6425445d54a67b2a23ae2ce37a7d642 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Tue, 10 Dec 2019 11:39:25 -0800 Subject: [PATCH 3/6] Generic Type support for classes Fixed typos after refactor --- src/diagrams/class/classDiagram.spec.js | 2 +- src/diagrams/class/classRenderer.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diagrams/class/classDiagram.spec.js b/src/diagrams/class/classDiagram.spec.js index 1d13f5158..47e983dd6 100644 --- a/src/diagrams/class/classDiagram.spec.js +++ b/src/diagrams/class/classDiagram.spec.js @@ -361,7 +361,7 @@ describe('class diagram, ', function () { const relations = parser.yy.getRelations(); expect(parser.yy.getClass('Class01').id).toBe('Class01'); - expect(parser.yy.getClass('Class01').genericType).toBe('T'); + expect(parser.yy.getClass('Class01').type).toBe('T'); expect(parser.yy.getClass('Class02').id).toBe('Class02'); expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); expect(relations[0].relation.type2).toBe('none'); diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index 67f78aa5b..5287c5ee9 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -320,8 +320,8 @@ const drawClass = function(elem, classDef) { let classTitleString = classDef.id; - if (classDef.genericType !== undefined && classDef.genericType !== '') { - classTitleString += '<' + classDef.genericType + '>'; + if (classDef.type !== undefined && classDef.type !== '') { + classTitleString += '<' + classDef.type + '>'; } // add class title From 9fbcc5c32d9ba837ddb94d1214abbb46ccc12007 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Tue, 10 Dec 2019 15:12:37 -0800 Subject: [PATCH 4/6] Code style fix --- src/diagrams/class/classDb.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diagrams/class/classDb.js b/src/diagrams/class/classDb.js index 616dba4e6..0e77450ba 100644 --- a/src/diagrams/class/classDb.js +++ b/src/diagrams/class/classDb.js @@ -7,7 +7,7 @@ const splitClassNameAndType = function(id) { let genericType = ''; let className = id; - if(id.indexOf('~') > 0) { + if (id.indexOf('~') > 0) { let split = id.split('~'); className = split[0]; genericType = split[1]; From 4b781d382767e230e54f0e1151477041931556b6 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Tue, 10 Dec 2019 15:18:26 -0800 Subject: [PATCH 5/6] remove extra space --- src/diagrams/class/classDb.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diagrams/class/classDb.js b/src/diagrams/class/classDb.js index 0e77450ba..e9a995a24 100644 --- a/src/diagrams/class/classDb.js +++ b/src/diagrams/class/classDb.js @@ -5,8 +5,8 @@ let classes = {}; const splitClassNameAndType = function(id) { let genericType = ''; - let className = id; - + let className = id; + if (id.indexOf('~') > 0) { let split = id.split('~'); className = split[0]; From 74c8e7fad900205268b9e45282f92518f57a6cf9 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Tue, 10 Dec 2019 15:21:25 -0800 Subject: [PATCH 6/6] another style fix --- src/diagrams/class/classDb.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diagrams/class/classDb.js b/src/diagrams/class/classDb.js index e9a995a24..6b6f256a5 100644 --- a/src/diagrams/class/classDb.js +++ b/src/diagrams/class/classDb.js @@ -5,7 +5,7 @@ let classes = {}; const splitClassNameAndType = function(id) { let genericType = ''; - let className = id; + let className = id; if (id.indexOf('~') > 0) { let split = id.split('~');