diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md index ed15922f13..0327b96fe0 100644 --- a/docs/syntax/classDiagram.md +++ b/docs/syntax/classDiagram.md @@ -285,8 +285,9 @@ To describe the visibility (or encapsulation) of an attribute or method/function > _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()` or after the return type: > -> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*` > - `$` Static e.g.: `someStaticMethod()$` or `someStaticMethod() String$` +> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*` +> - `$*` Both e.g: `someAbstractMethod()$*` or `someAbstractMethod() int$*` > _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the very end: > diff --git a/packages/mermaid/src/diagrams/class/classTypes.spec.ts b/packages/mermaid/src/diagrams/class/classTypes.spec.ts index 5a5ffa4dbd..8fc0866028 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.spec.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.spec.ts @@ -4,6 +4,7 @@ const spyOn = vi.spyOn; const staticCssStyle = 'text-decoration:underline;'; const abstractCssStyle = 'font-style:italic;'; +const staticAbstractCssStyle = 'text-decoration:underline;font-style:italic;'; describe('given text representing a method, ', function () { describe('when method has no parameters', function () { @@ -57,6 +58,22 @@ describe('given text representing a method, ', function () { expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + + it('should return correct css for static and abstract classifier', function () { + const str = `getTime()$*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); + + it('should return correct css for abstract and static classifier', function () { + const str = `getTime()*$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); }); describe('when method has single parameter value', function () { @@ -110,6 +127,21 @@ describe('given text representing a method, ', function () { expect(classMember.getDisplayDetails().displayText).toBe('getTime(int)'); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + + it('should return correct css for static and abstract classifier', function () { + const str = `getTime(int)$*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(int)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); + it('should return correct css for abstract and static classifier', function () { + const str = `getTime(int)*$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(int)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); }); describe('when method has single parameter type and name (type first)', function () { @@ -163,6 +195,22 @@ describe('given text representing a method, ', function () { expect(classMember.getDisplayDetails().displayText).toBe('getTime(int count)'); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + + it('should return correct css for static and abstract classifier', function () { + const str = `getTime(int count)$*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(int count)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); + + it('should return correct css for abstract classifier', function () { + const str = `getTime(int count)*$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(int count)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); }); describe('when method has single parameter type and name (name first)', function () { @@ -216,6 +264,22 @@ describe('given text representing a method, ', function () { expect(classMember.getDisplayDetails().displayText).toBe('getTime(count int)'); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + + it('should return correct css for static and abstract classifier', function () { + const str = `getTime(count int)$*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(count int)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); + + it('should return correct css for abstract and static classifier', function () { + const str = `getTime(count int)*$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(count int)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); }); describe('when method has multiple parameters', function () { @@ -269,6 +333,22 @@ describe('given text representing a method, ', function () { expect(classMember.getDisplayDetails().displayText).toBe('getTime(string text, int count)'); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + + it('should return correct css for static and abstract classifier', function () { + const str = `getTime(string text, int count)$*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(string text, int count)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); + + it('should return correct css for abstract and static classifier', function () { + const str = `getTime(string text, int count)*$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(string text, int count)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); }); describe('when method has return type', function () { @@ -322,6 +402,22 @@ describe('given text representing a method, ', function () { expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime'); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + + it('should return correct css for static and abstract classifier', function () { + const str = `getTime() DateTime$*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); + + it('should return correct css for abstract and static classifier', function () { + const str = `getTime() DateTime*$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); }); describe('when method parameter is generic', function () { @@ -375,6 +471,22 @@ describe('given text representing a method, ', function () { expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List)'); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + + it('should return correct css for static and abstract classifier', function () { + const str = `getTimes(List~T~)$*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); + + it('should return correct css for abstract and static classifier', function () { + const str = `getTimes(List~T~)*$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); }); describe('when method parameter contains two generic', function () { @@ -428,6 +540,22 @@ describe('given text representing a method, ', function () { expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List, List)'); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + + it('should return correct css for static and abstract classifier', function () { + const str = `getTimes(List~T~, List~OT~)$*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List, List)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); + + it('should return correct css for abstract and static classifier', function () { + const str = `getTimes(List~T~, List~OT~)*$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List, List)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); }); describe('when method parameter is a nested generic', function () { @@ -481,6 +609,22 @@ describe('given text representing a method, ', function () { expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List>)'); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + + it('should return correct css for static and abstract classifier', function () { + const str = `getTimetableList(List~List~T~~)$*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List>)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); + + it('should return correct css for abstract and static classifier', function () { + const str = `getTimetableList(List~List~T~~)*$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List>)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); }); describe('when method parameter is a composite generic', function () { @@ -540,6 +684,20 @@ describe('given text representing a method, ', function () { expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + + it('should return correct css for static and abstract classifier', function () { + const str = methodNameAndParameters + '$*'; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); + + it('should return correct css for abstract and static classifier', function () { + const str = methodNameAndParameters + '*$'; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); }); describe('when method return type is generic', function () { @@ -593,6 +751,22 @@ describe('given text representing a method, ', function () { expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List'); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + + it('should return correct css for static and abstract classifier', function () { + const str = `getTimes() List~T~$*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); + + it('should return correct css for abstract and static classifier', function () { + const str = `getTimes() List~T~*$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); }); describe('when method return type is a nested generic', function () { @@ -660,6 +834,26 @@ describe('given text representing a method, ', function () { ); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + + it('should return correct css for static and abstract classifier', function () { + const str = `getTimetableList() List~List~T~~$*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + 'getTimetableList() : List>' + ); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); + + it('should return correct css for abstract and static classifier', function () { + const str = `getTimetableList() List~List~T~~*$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + 'getTimetableList() : List>' + ); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle); + }); }); describe('--uncategorized tests--', function () { @@ -759,4 +953,26 @@ describe('given text representing an attribute', () => { expect(displayDetails.cssStyle).toBe(abstractCssStyle); }); }); + + describe('when the attribute has static and abstract "$*" modifier', () => { + it('should parse the display text correctly and apply abstract css style', () => { + const str = 'name String$*'; + + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('name String'); + expect(displayDetails.cssStyle).toBe(staticAbstractCssStyle); + }); + }); + + describe('when the attribute has abstract and static "*$" modifier', () => { + it('should parse the display text correctly and apply abstract css style', () => { + const str = 'name String*$'; + + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('name String'); + expect(displayDetails.cssStyle).toBe(staticAbstractCssStyle); + }); + }); }); diff --git a/packages/mermaid/src/diagrams/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts index 85be3a4e87..2b58a88b5c 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.ts @@ -76,45 +76,28 @@ export class ClassMember { let potentialClassifier = ''; if (this.memberType === 'method') { - const methodRegEx = /([#+~-])?(.+)\((.*)\)([\s$*])?(.*)([$*])?/; + const methodRegEx = /([#+~-])?(.+)\((.*)\)([$*]{0,2})(.*?)([$*]{0,2})$/; const match = input.match(methodRegEx); if (match) { - const detectedVisibility = match[1] ? match[1].trim() : ''; - - if (visibilityValues.includes(detectedVisibility)) { - this.visibility = detectedVisibility as Visibility; - } - + this.visibility = (match[1] ? match[1].trim() : '') as Visibility; this.id = match[2].trim(); this.parameters = match[3] ? match[3].trim() : ''; potentialClassifier = match[4] ? match[4].trim() : ''; this.returnType = match[5] ? match[5].trim() : ''; if (potentialClassifier === '') { - const lastChar = this.returnType.substring(this.returnType.length - 1); - if (lastChar.match(/[$*]/)) { - potentialClassifier = lastChar; - this.returnType = this.returnType.substring(0, this.returnType.length - 1); - } + potentialClassifier = match[6] ? match[6].trim() : ''; } } } else { - const length = input.length; - const firstChar = input.substring(0, 1); - const lastChar = input.substring(length - 1); + const fieldRegEx = /([#+~-])?(.*?)([$*]{0,2})$/; + const match = input.match(fieldRegEx); - if (visibilityValues.includes(firstChar)) { - this.visibility = firstChar as Visibility; - } - - if (lastChar.match(/[$*]/)) { - potentialClassifier = lastChar; + if (match) { + this.visibility = (match[1] ? match[1].trim() : '') as Visibility; + this.id = match[2] ? match[2].trim() : ''; + potentialClassifier = match[3] ? match[3].trim() : ''; } - - this.id = input.substring( - this.visibility === '' ? 0 : 1, - potentialClassifier === '' ? length : length - 1 - ); } this.classifier = potentialClassifier; @@ -122,10 +105,13 @@ export class ClassMember { parseClassifier() { switch (this.classifier) { - case '*': - return 'font-style:italic;'; case '$': return 'text-decoration:underline;'; + case '*': + return 'font-style:italic;'; + case '$*': + case '*$': + return 'text-decoration:underline;font-style:italic;'; default: return ''; } diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md index 029d11b540..d856f90563 100644 --- a/packages/mermaid/src/docs/syntax/classDiagram.md +++ b/packages/mermaid/src/docs/syntax/classDiagram.md @@ -173,8 +173,9 @@ To describe the visibility (or encapsulation) of an attribute or method/function > _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()` or after the return type: > -> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*` > - `$` Static e.g.: `someStaticMethod()$` or `someStaticMethod() String$` +> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*` +> - `$*` Both e.g: `someAbstractMethod()$*` or `someAbstractMethod() int$*` > _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the very end: >