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