diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js index 3f022b55de..078094519a 100644 --- a/cypress/integration/rendering/classDiagram.spec.js +++ b/cypress/integration/rendering/classDiagram.spec.js @@ -274,4 +274,19 @@ describe('Class diagram', () => { ); cy.get('svg'); }); + + it('11: should render a simple class diagram with return type on method', () => { + imgSnapshotTest( + ` + classDiagram + class Class10~T~ { + int[] id + test(int[] ids) bool + testArray() bool[] + } + `, + {} + ); + cy.get('svg'); + }); }); diff --git a/docs/classDiagram.md b/docs/classDiagram.md index 8ed305ad4c..f0b3c3cf7c 100644 --- a/docs/classDiagram.md +++ b/docs/classDiagram.md @@ -105,7 +105,7 @@ Naming convention: a class name should be composed of alphanumeric (unicode allo UML provides mechanisms to represent class members, such as attributes and methods, and additional information about them. -Mermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The ones with `()` are treated as functions/methods, and others as attributes. +Mermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The ones with `()` are treated as functions/methods, and others as attributes. To indicate a return type for a method, enclose the type within **square brackets** `[]` There are two ways to define the members of a class, and regardless of whichever syntax is used to define the members, the output will still be same. The two different ways are : @@ -115,8 +115,8 @@ There are two ways to define the members of a class, and regardless of whichever class BankAccount BankAccount : +String owner BankAccount : +BigDecimal balance - BankAccount : +deposit(amount) - BankAccount : +withdrawl(amount) + BankAccount : +deposit(amount) bool + BankAccount : +withdrawal(amount) ``` ``` mermaid @@ -124,7 +124,7 @@ There are two ways to define the members of a class, and regardless of whichever class BankAccount BankAccount : +String owner BankAccount : +BigDecimal balance - BankAccount : +deposit(amount) + BankAccount : +deposit(amount) : bool BankAccount : +withdrawl(amount) ``` @@ -133,7 +133,7 @@ There are two ways to define the members of a class, and regardless of whichever class BankAccount{ +String owner +BigDecimal balance - +deposit(amount) + +deposit(amount) bool +withdrawl(amount) } ``` @@ -142,12 +142,15 @@ class BankAccount{ class BankAccount{ +String owner +BigDecimal balance - +deposit(amount) + +deposit(amount) : bool +withdrawl(amount) } ``` +#### Return Type +Optionally you can end the method/function definition with the data type that will be returned + #### Visibility To specify the visibility of a class member (i.e. any attribute or method), these notations may be placed before the member's name, but it is optional: diff --git a/src/diagrams/class/classDiagram.spec.js b/src/diagrams/class/classDiagram.spec.js index 89bfdc01b8..8510b086d2 100644 --- a/src/diagrams/class/classDiagram.spec.js +++ b/src/diagrams/class/classDiagram.spec.js @@ -105,7 +105,7 @@ describe('class diagram, ', function () { parser.parse(str); }); - it('should handle parsing of method statements grouped by brackets', function () { + it('should handle parsing of method statements grouped by brackets', function () { const str = 'classDiagram\n' + 'class Dummy_Class {\n' + @@ -121,6 +121,36 @@ describe('class diagram, ', function () { parser.parse(str); }); + it('should handle return types on methods', function () { + const str = + 'classDiagram\n' + + 'Object <|-- ArrayList\n' + + 'Object : equals()\n' + + 'Object : -Object[] objects\n' + + 'Object : +getObjects() Object[]\n' + + 'ArrayList : Dummy elementData\n' + + 'ArrayList : getDummy() Dummy'; + + parser.parse(str); + }); + + it('should handle return types on methods grouped by brackets', function () { + const str = + 'classDiagram\n' + + 'class Dummy_Class {\n' + + 'string data\n' + + 'getDummy() Dummy\n' + + '}\n' + + '\n' + + 'class Flight {\n' + + ' int flightNumber\n' + + ' datetime departureTime\n' + + ' getDepartureTime() datetime\n' + + '}'; + + parser.parse(str); + }); + it('should handle parsing of separators', function () { const str = 'classDiagram\n' + diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index ca4bfba782..39a15a5bd3 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -288,23 +288,14 @@ const drawClass = function(elem, classDef) { } const addTspan = function(textEl, txt, isFirst) { + let isMethod = txt.indexOf(')') > 1; let displayText = txt; let cssStyle = ''; - let methodEnd = txt.indexOf(')') + 1; - - if (methodEnd > 1 && methodEnd <= txt.length) { - let classifier = txt.substring(methodEnd); - - switch (classifier) { - case '*': - cssStyle = 'font-style:italic;'; - break; - case '$': - cssStyle = 'text-decoration:underline;'; - break; - } - displayText = txt.substring(0, methodEnd); + if (isMethod) { + let method = buildDisplayTextForMethod(txt); + displayText = method.displayText; + cssStyle = method.cssStyle; } const tSpan = textEl @@ -321,6 +312,50 @@ const drawClass = function(elem, classDef) { } }; + const buildDisplayTextForMethod = function(txt) { + let regEx = /(\+|-|~|#)?(\w+)\s?\((\w+(<\w+>|\[\])?\s?(\w+)?)?\)\s?([*|$])?\s?(\w+(<\w+>|\[\])?)?/; + + let cssStyle = ''; + let displayText = txt; + let methodName = txt; + let classifier = ''; + + let parsedText = txt.match(regEx); + + if (parsedText) { + let visibility = parsedText[1] ? parsedText[1].trim() : ''; + methodName = parsedText[2] ? parsedText[2].trim() : ''; + let parameters = parsedText[3] ? parsedText[3].trim() : ''; + classifier = parsedText[6] ? parsedText[6].trim() : ''; + let returnType = parsedText[7] ? ' : ' + parsedText[7].trim() : ''; + + displayText = visibility + methodName + '(' + parameters + ')' + returnType; + } else { + let methodEnd = displayText.indexOf(')') + 1; + classifier = displayText.substring(methodEnd, methodEnd + 1); + if (classifier !== '' && classifier !== ' ') { + displayText = displayText.replace(classifier, ''); + } + } + + switch (classifier) { + case '*': + cssStyle = 'font-style:italic;'; + break; + case '$': + cssStyle = 'text-decoration:underline;'; + break; + } + + let method = { + methodname: methodName, + displayText: displayText, + cssStyle: cssStyle + }; + + return method; + }; + const id = classDef.id; const classInfo = { id: id,