From d7771eb4b6ff6460596c030ff079d9ff25b7f6e3 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 6 Jan 2020 10:12:29 -0800 Subject: [PATCH 1/5] 1119 Add ability to define return type for methods Updated ClassRenderer to check for `[]` to indicate return type for method. Small refactor to split out logic for determining method display text and style. Updated documentation --- docs/classDiagram.md | 12 +++---- src/diagrams/class/classRenderer.js | 53 +++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/docs/classDiagram.md b/docs/classDiagram.md index dfadabdbf..814fee196 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,15 +115,15 @@ 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 classDiagram class BankAccount BankAccount : +String owner BankAccount : +BigDecimal balance - BankAccount : +deposit(amount) + BankAccount : +deposit(amount) : bool BankAccount : +withdrawl(amount) ``` @@ -132,7 +132,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) } ``` @@ -141,7 +141,7 @@ class BankAccount{ class BankAccount{ +String owner +BigDecimal balance - +deposit(amount) + +deposit(amount) : bool +withdrawl(amount) } ``` diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index ca4bfba78..43c1d944f 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 @@ -322,6 +313,40 @@ const drawClass = function(elem, classDef) { }; const id = classDef.id; + const buildDisplayTextForMethod = function(txt) { + let cssStyle = ''; + let methodEnd = txt.indexOf(')') + 1; + let methodName = txt.substring(0, methodEnd); + + let classifier = txt.substring(methodEnd, methodEnd + 1); + + switch (classifier) { + case '*': + cssStyle = 'font-style:italic;'; + break; + case '$': + cssStyle = 'text-decoration:underline;'; + break; + } + + let method = { + methodname: methodName, + displayText: methodName, + cssStyle: cssStyle + }; + + let returnTypeStart = txt.indexOf('[') + 1; + let returnTypeEnd = txt.indexOf(']'); + + if (returnTypeStart > 1 && returnTypeEnd > returnTypeStart) { + let returnType = txt.substring(returnTypeStart, returnTypeEnd); + + method.displayText = methodName + ' : ' + returnType; + } + + return method; + } + const classInfo = { id: id, label: classDef.id, From 0af5e0b7955034d16945ed3950e31c0ed347fca6 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 6 Jan 2020 12:26:36 -0800 Subject: [PATCH 2/5] Address code style issues --- src/diagrams/class/classRenderer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index 43c1d944f..181df2ece 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -343,9 +343,9 @@ const drawClass = function(elem, classDef) { method.displayText = methodName + ' : ' + returnType; } - + return method; - } + }; const classInfo = { id: id, From 58fbfc3c38b8f79ec1c88e9b462c313b2811d121 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 6 Jan 2020 16:21:11 -0800 Subject: [PATCH 3/5] 1119 Support method return types Small refactor to split out logic for determining method display text and style. Updated documentation Used regex to parse method statements in class diagrams to extract discrete elements to set display appropriately. Added tests and updated docs --- .../rendering/classDiagram.spec.js | 15 +++++++++ docs/classDiagram.md | 7 ++-- src/diagrams/class/classDiagram.spec.js | 32 ++++++++++++++++++- src/diagrams/class/classRenderer.js | 30 +++++++++-------- 4 files changed, 68 insertions(+), 16 deletions(-) diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js index 3f022b55d..078094519 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 814fee196..f13d60d83 100644 --- a/docs/classDiagram.md +++ b/docs/classDiagram.md @@ -115,7 +115,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) [bool] + BankAccount : +deposit(amount) bool BankAccount : +withdrawal(amount) ``` ``` mermaid @@ -132,7 +132,7 @@ There are two ways to define the members of a class, and regardless of whichever class BankAccount{ +String owner +BigDecimal balance - +deposit(amount) [bool] + +deposit(amount) bool +withdrawl(amount) } ``` @@ -147,6 +147,9 @@ class BankAccount{ ``` +#### 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 89bfdc01b..8510b086d 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 181df2ece..f90e3d620 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -314,11 +314,24 @@ const drawClass = function(elem, classDef) { const id = classDef.id; const buildDisplayTextForMethod = function(txt) { + let regEx = /(\+|-|~|#)?(\w+)\((\w+\[?\]?)?\s?(\w+)?\)([*|$])?\s?(\w+\[?\]?)?/; let cssStyle = ''; - let methodEnd = txt.indexOf(')') + 1; - let methodName = txt.substring(0, methodEnd); + let displayText = txt; + let methodName = txt; + let classifier = ''; - let classifier = txt.substring(methodEnd, methodEnd + 1); + let parsedText = txt.match(regEx); + + if (parsedText) { + let visibility = parsedText[1] ? parsedText[1] : ''; + methodName = parsedText[2] ? parsedText[2] : ''; + let parameterType = parsedText[3] ? parsedText[3] : ''; + let parameterName = parsedText[4] ? parsedText[4] : ''; + classifier = parsedText[5] ? parsedText[5] : ''; + let returnType = parsedText[6] ? ' : ' + parsedText[6] : ''; + displayText = + visibility + methodName + '(' + parameterType + ' ' + parameterName + ')' + returnType; + } switch (classifier) { case '*': @@ -331,19 +344,10 @@ const drawClass = function(elem, classDef) { let method = { methodname: methodName, - displayText: methodName, + displayText: displayText, cssStyle: cssStyle }; - let returnTypeStart = txt.indexOf('[') + 1; - let returnTypeEnd = txt.indexOf(']'); - - if (returnTypeStart > 1 && returnTypeEnd > returnTypeStart) { - let returnType = txt.substring(returnTypeStart, returnTypeEnd); - - method.displayText = methodName + ' : ' + returnType; - } - return method; }; From 57b5b9a7a67b7bc9a2f53a178ac62bbeeda79aa5 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 6 Jan 2020 16:37:08 -0800 Subject: [PATCH 4/5] Added conditional to fallback to old style --- src/diagrams/class/classRenderer.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index f90e3d620..003c962fb 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -331,6 +331,12 @@ const drawClass = function(elem, classDef) { let returnType = parsedText[6] ? ' : ' + parsedText[6] : ''; displayText = visibility + methodName + '(' + parameterType + ' ' + parameterName + ')' + returnType; + } else { + let methodEnd = displayText.indexOf(')') + 1; + classifier = displayText.substring(methodEnd, methodEnd + 1); + if (classifier !== '' && classifier !== ' ') { + displayText = displayText.replace(classifier, ''); + } } switch (classifier) { From fa1331ffd5a50412d492a274e03964e7b3dfa966 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Fri, 10 Jan 2020 10:24:04 -0800 Subject: [PATCH 5/5] Fix after removing other code --- src/diagrams/class/classRenderer.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index 003c962fb..39a15a5bd 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -312,9 +312,9 @@ const drawClass = function(elem, classDef) { } }; - const id = classDef.id; const buildDisplayTextForMethod = function(txt) { - let regEx = /(\+|-|~|#)?(\w+)\((\w+\[?\]?)?\s?(\w+)?\)([*|$])?\s?(\w+\[?\]?)?/; + let regEx = /(\+|-|~|#)?(\w+)\s?\((\w+(<\w+>|\[\])?\s?(\w+)?)?\)\s?([*|$])?\s?(\w+(<\w+>|\[\])?)?/; + let cssStyle = ''; let displayText = txt; let methodName = txt; @@ -323,14 +323,13 @@ const drawClass = function(elem, classDef) { let parsedText = txt.match(regEx); if (parsedText) { - let visibility = parsedText[1] ? parsedText[1] : ''; - methodName = parsedText[2] ? parsedText[2] : ''; - let parameterType = parsedText[3] ? parsedText[3] : ''; - let parameterName = parsedText[4] ? parsedText[4] : ''; - classifier = parsedText[5] ? parsedText[5] : ''; - let returnType = parsedText[6] ? ' : ' + parsedText[6] : ''; - displayText = - visibility + methodName + '(' + parameterType + ' ' + parameterName + ')' + returnType; + 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); @@ -357,6 +356,7 @@ const drawClass = function(elem, classDef) { return method; }; + const id = classDef.id; const classInfo = { id: id, label: classDef.id,