mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-31 14:16:42 +02:00
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<T>`
This commit is contained in:
@@ -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');
|
||||
});
|
||||
});
|
||||
|
37
dist/index.html
vendored
37
dist/index.html
vendored
@@ -418,6 +418,43 @@ class Class10 {
|
||||
size()
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mermaid">
|
||||
classDiagram
|
||||
class Class01~T~
|
||||
Class01 : #size()
|
||||
Class01 : -int chimp
|
||||
Class01 : +int gorilla
|
||||
class Class10~T~ {
|
||||
<<service>>
|
||||
int id
|
||||
size()
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mermaid">
|
||||
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()
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mermaid">
|
||||
stateDiagram
|
||||
State1
|
||||
|
@@ -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();
|
||||
|
@@ -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' + '<<interface>> Class1';
|
||||
parser.parse(str);
|
||||
|
@@ -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
|
||||
|
@@ -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");
|
||||
<generic>[~] this.popState();
|
||||
<generic>[^~]* return "GENERICTYPE";
|
||||
["] this.begin("string");
|
||||
<string>["] this.popState();
|
||||
<string>[^"]* return "STR";
|
||||
@@ -136,6 +139,8 @@ statements
|
||||
className
|
||||
: alphaNumToken className { $$=$1+$2; }
|
||||
| alphaNumToken { $$=$1; }
|
||||
| alphaNumToken GENERICTYPE className { $$=$1+'~'+$2+$3; }
|
||||
| alphaNumToken GENERICTYPE { $$=$1+'~'+$2; }
|
||||
;
|
||||
|
||||
statement
|
||||
|
Reference in New Issue
Block a user