diff --git a/packages/mermaid/src/diagrams/requirement/requirementDb.ts b/packages/mermaid/src/diagrams/requirement/requirementDb.ts index 76787fd8f..972c5e44e 100644 --- a/packages/mermaid/src/diagrams/requirement/requirementDb.ts +++ b/packages/mermaid/src/diagrams/requirement/requirementDb.ts @@ -1,4 +1,5 @@ import { getConfig } from '../../diagram-api/diagramAPI.js'; +import type { DiagramDB } from '../../diagram-api/types.js'; import { log } from '../../logger.js'; import type { Node, Edge } from '../../rendering-util/types.js'; @@ -22,320 +23,327 @@ import type { VerifyType, } from './types.js'; -const RequirementType = { - REQUIREMENT: 'Requirement', - FUNCTIONAL_REQUIREMENT: 'Functional Requirement', - INTERFACE_REQUIREMENT: 'Interface Requirement', - PERFORMANCE_REQUIREMENT: 'Performance Requirement', - PHYSICAL_REQUIREMENT: 'Physical Requirement', - DESIGN_CONSTRAINT: 'Design Constraint', -}; +export class RequirementDB implements DiagramDB { + private relations: Relation[] = []; + private latestRequirement: Requirement = this.getInitialRequirement(); + private requirements = new Map(); + private latestElement: Element = this.getInitialElement(); + private elements = new Map(); + private classes = new Map(); + private direction = 'TB'; -const RiskLevel = { - LOW_RISK: 'Low', - MED_RISK: 'Medium', - HIGH_RISK: 'High', -}; + private RequirementType = { + REQUIREMENT: 'Requirement', + FUNCTIONAL_REQUIREMENT: 'Functional Requirement', + INTERFACE_REQUIREMENT: 'Interface Requirement', + PERFORMANCE_REQUIREMENT: 'Performance Requirement', + PHYSICAL_REQUIREMENT: 'Physical Requirement', + DESIGN_CONSTRAINT: 'Design Constraint', + }; -const VerifyType = { - VERIFY_ANALYSIS: 'Analysis', - VERIFY_DEMONSTRATION: 'Demonstration', - VERIFY_INSPECTION: 'Inspection', - VERIFY_TEST: 'Test', -}; + private RiskLevel = { + LOW_RISK: 'Low', + MED_RISK: 'Medium', + HIGH_RISK: 'High', + }; -const Relationships = { - CONTAINS: 'contains', - COPIES: 'copies', - DERIVES: 'derives', - SATISFIES: 'satisfies', - VERIFIES: 'verifies', - REFINES: 'refines', - TRACES: 'traces', -}; + private VerifyType = { + VERIFY_ANALYSIS: 'Analysis', + VERIFY_DEMONSTRATION: 'Demonstration', + VERIFY_INSPECTION: 'Inspection', + VERIFY_TEST: 'Test', + }; -let direction = 'TB'; -const getDirection = () => direction; -const setDirection = (dir: string) => { - direction = dir; -}; + private Relationships = { + CONTAINS: 'contains', + COPIES: 'copies', + DERIVES: 'derives', + SATISFIES: 'satisfies', + VERIFIES: 'verifies', + REFINES: 'refines', + TRACES: 'traces', + }; -const getInitialRequirement = (): Requirement => ({ - requirementId: '', - text: '', - risk: '' as RiskLevel, - verifyMethod: '' as VerifyType, - name: '', - type: '' as RequirementType, - cssStyles: [], - classes: ['default'], -}); + constructor() { + this.clear(); -const getInitialElement = (): Element => ({ - name: '', - type: '', - docRef: '', - cssStyles: [], - classes: ['default'], -}); + // Needed for JISON since it only supports direct properties + this.setDirection = this.setDirection.bind(this); + this.addRequirement = this.addRequirement.bind(this); + this.setNewReqId = this.setNewReqId.bind(this); + this.setNewReqRisk = this.setNewReqRisk.bind(this); + this.setNewReqText = this.setNewReqText.bind(this); + this.setNewReqVerifyMethod = this.setNewReqVerifyMethod.bind(this); + this.addElement = this.addElement.bind(this); + this.setNewElementType = this.setNewElementType.bind(this); + this.setNewElementDocRef = this.setNewElementDocRef.bind(this); + this.addRelationship = this.addRelationship.bind(this); + this.setCssStyle = this.setCssStyle.bind(this); + this.setClass = this.setClass.bind(this); + this.defineClass = this.defineClass.bind(this); + this.setAccTitle = this.setAccTitle.bind(this); + this.setAccDescription = this.setAccDescription.bind(this); + } -// Update initial declarations -let relations: Relation[] = []; -let latestRequirement: Requirement = getInitialRequirement(); -let requirements = new Map(); -let latestElement: Element = getInitialElement(); -let elements = new Map(); -let classes = new Map(); + public getDirection() { + return this.direction; + } + public setDirection(dir: string) { + this.direction = dir; + } -// Add reset functions -const resetLatestRequirement = () => { - latestRequirement = getInitialRequirement(); -}; + private resetLatestRequirement() { + this.latestRequirement = this.getInitialRequirement(); + } -const resetLatestElement = () => { - latestElement = getInitialElement(); -}; + private resetLatestElement() { + this.latestElement = this.getInitialElement(); + } -const addRequirement = (name: string, type: RequirementType) => { - if (!requirements.has(name)) { - requirements.set(name, { - name, - type, - requirementId: latestRequirement.requirementId, - text: latestRequirement.text, - risk: latestRequirement.risk, - verifyMethod: latestRequirement.verifyMethod, + private getInitialRequirement(): Requirement { + return { + requirementId: '', + text: '', + risk: '' as RiskLevel, + verifyMethod: '' as VerifyType, + name: '', + type: '' as RequirementType, cssStyles: [], classes: ['default'], - }); + }; } - resetLatestRequirement(); - return requirements.get(name); -}; - -const getRequirements = () => requirements; - -const setNewReqId = (id: string) => { - if (latestRequirement !== undefined) { - latestRequirement.requirementId = id; - } -}; - -const setNewReqText = (text: string) => { - if (latestRequirement !== undefined) { - latestRequirement.text = text; - } -}; - -const setNewReqRisk = (risk: RiskLevel) => { - if (latestRequirement !== undefined) { - latestRequirement.risk = risk; - } -}; - -const setNewReqVerifyMethod = (verifyMethod: VerifyType) => { - if (latestRequirement !== undefined) { - latestRequirement.verifyMethod = verifyMethod; - } -}; - -const addElement = (name: string) => { - if (!elements.has(name)) { - elements.set(name, { - name, - type: latestElement.type, - docRef: latestElement.docRef, + private getInitialElement(): Element { + return { + name: '', + type: '', + docRef: '', cssStyles: [], classes: ['default'], - }); - log.info('Added new element: ', name); + }; } - resetLatestElement(); - return elements.get(name); -}; - -const getElements = () => elements; - -const setNewElementType = (type: string) => { - if (latestElement !== undefined) { - latestElement.type = type; - } -}; - -const setNewElementDocRef = (docRef: string) => { - if (latestElement !== undefined) { - latestElement.docRef = docRef; - } -}; - -const addRelationship = (type: RelationshipType, src: string, dst: string) => { - relations.push({ - type, - src, - dst, - }); -}; - -const getRelationships = () => relations; - -const clear = () => { - relations = []; - resetLatestRequirement(); - requirements = new Map(); - resetLatestElement(); - elements = new Map(); - classes = new Map(); - commonClear(); -}; - -export const setCssStyle = function (ids: string[], styles: string[]) { - for (const id of ids) { - const node = requirements.get(id) ?? elements.get(id); - if (!styles || !node) { - return; - } - for (const s of styles) { - if (s.includes(',')) { - node.cssStyles.push(...s.split(',')); - } else { - node.cssStyles.push(s); - } - } - } -}; - -export const setClass = function (ids: string[], classNames: string[]) { - for (const id of ids) { - const node = requirements.get(id) ?? elements.get(id); - if (node) { - for (const _class of classNames) { - node.classes.push(_class); - const styles = classes.get(_class)?.styles; - if (styles) { - node.cssStyles.push(...styles); - } - } - } - } -}; - -export const defineClass = function (ids: string[], style: string[]) { - for (const id of ids) { - let styleClass = classes.get(id); - if (styleClass === undefined) { - styleClass = { id, styles: [], textStyles: [] }; - classes.set(id, styleClass); - } - - if (style) { - style.forEach(function (s) { - if (/color/.exec(s)) { - const newStyle = s.replace('fill', 'bgFill'); // .replace('color', 'fill'); - styleClass.textStyles.push(newStyle); - } - styleClass.styles.push(s); + public addRequirement(name: string, type: RequirementType) { + if (!this.requirements.has(name)) { + this.requirements.set(name, { + name, + type, + requirementId: this.latestRequirement.requirementId, + text: this.latestRequirement.text, + risk: this.latestRequirement.risk, + verifyMethod: this.latestRequirement.verifyMethod, + cssStyles: [], + classes: ['default'], }); } + this.resetLatestRequirement(); - requirements.forEach((value) => { - if (value.classes.includes(id)) { - value.cssStyles.push(...style.flatMap((s) => s.split(','))); - } - }); - elements.forEach((value) => { - if (value.classes.includes(id)) { - value.cssStyles.push(...style.flatMap((s) => s.split(','))); - } + return this.requirements.get(name); + } + + public getRequirements() { + return this.requirements; + } + + public setNewReqId(id: string) { + if (this.latestRequirement !== undefined) { + this.latestRequirement.requirementId = id; + } + } + + public setNewReqText(text: string) { + if (this.latestRequirement !== undefined) { + this.latestRequirement.text = text; + } + } + + public setNewReqRisk(risk: RiskLevel) { + if (this.latestRequirement !== undefined) { + this.latestRequirement.risk = risk; + } + } + + public setNewReqVerifyMethod(verifyMethod: VerifyType) { + if (this.latestRequirement !== undefined) { + this.latestRequirement.verifyMethod = verifyMethod; + } + } + + public addElement(name: string) { + if (!this.elements.has(name)) { + this.elements.set(name, { + name, + type: this.latestElement.type, + docRef: this.latestElement.docRef, + cssStyles: [], + classes: ['default'], + }); + log.info('Added new element: ', name); + } + this.resetLatestElement(); + + return this.elements.get(name); + } + + public getElements() { + return this.elements; + } + + public setNewElementType(type: string) { + if (this.latestElement !== undefined) { + this.latestElement.type = type; + } + } + + public setNewElementDocRef(docRef: string) { + if (this.latestElement !== undefined) { + this.latestElement.docRef = docRef; + } + } + + public addRelationship(type: RelationshipType, src: string, dst: string) { + this.relations.push({ + type, + src, + dst, }); } -}; -export const getClasses = () => { - return classes; -}; - -const getData = () => { - const config = getConfig(); - const nodes: Node[] = []; - const edges: Edge[] = []; - for (const requirement of requirements.values()) { - const node = requirement as unknown as Node; - node.id = requirement.name; - node.cssStyles = requirement.cssStyles; - node.cssClasses = requirement.classes.join(' '); - node.shape = 'requirementBox'; - node.look = config.look; - nodes.push(node); + public getRelationships() { + return this.relations; } - for (const element of elements.values()) { - const node = element as unknown as Node; - node.shape = 'requirementBox'; - node.look = config.look; - node.id = element.name; - node.cssStyles = element.cssStyles; - node.cssClasses = element.classes.join(' '); - - nodes.push(node); + public clear() { + this.relations = []; + this.resetLatestRequirement(); + this.requirements = new Map(); + this.resetLatestElement(); + this.elements = new Map(); + this.classes = new Map(); + commonClear(); } - for (const relation of relations) { - let counter = 0; - const isContains = relation.type === Relationships.CONTAINS; - const edge: Edge = { - id: `${relation.src}-${relation.dst}-${counter}`, - start: requirements.get(relation.src)?.name ?? elements.get(relation.src)?.name, - end: requirements.get(relation.dst)?.name ?? elements.get(relation.dst)?.name, - label: `<<${relation.type}>>`, - classes: 'relationshipLine', - style: ['fill:none', isContains ? '' : 'stroke-dasharray: 10,7'], - labelpos: 'c', - thickness: 'normal', - type: 'normal', - pattern: isContains ? 'normal' : 'dashed', - arrowTypeEnd: isContains ? 'requirement_contains' : 'requirement_arrow', - look: config.look, - }; - - edges.push(edge); - counter++; + public setCssStyle(ids: string[], styles: string[]) { + for (const id of ids) { + const node = this.requirements.get(id) ?? this.elements.get(id); + if (!styles || !node) { + return; + } + for (const s of styles) { + if (s.includes(',')) { + node.cssStyles.push(...s.split(',')); + } else { + node.cssStyles.push(s); + } + } + } } - return { nodes, edges, other: {}, config, direction: getDirection() }; -}; + public setClass(ids: string[], classNames: string[]) { + for (const id of ids) { + const node = this.requirements.get(id) ?? this.elements.get(id); + if (node) { + for (const _class of classNames) { + node.classes.push(_class); + const styles = this.classes.get(_class)?.styles; + if (styles) { + node.cssStyles.push(...styles); + } + } + } + } + } -export default { - Relationships, - RequirementType, - RiskLevel, - VerifyType, - getConfig: () => getConfig().requirement, - addRequirement, - getRequirements, - setNewReqId, - setNewReqText, - setNewReqRisk, - setNewReqVerifyMethod, - setAccTitle, - getAccTitle, - setAccDescription, - getAccDescription, - setDiagramTitle, - getDiagramTitle, - getDirection, - setDirection, - addElement, - getElements, - setNewElementType, - setNewElementDocRef, - addRelationship, - getRelationships, - clear, - setCssStyle, - setClass, - defineClass, - getClasses, - getData, -}; + public defineClass(ids: string[], style: string[]) { + for (const id of ids) { + let styleClass = this.classes.get(id); + if (styleClass === undefined) { + styleClass = { id, styles: [], textStyles: [] }; + this.classes.set(id, styleClass); + } + + if (style) { + style.forEach(function (s) { + if (/color/.exec(s)) { + const newStyle = s.replace('fill', 'bgFill'); // .replace('color', 'fill'); + styleClass.textStyles.push(newStyle); + } + styleClass.styles.push(s); + }); + } + + this.requirements.forEach((value) => { + if (value.classes.includes(id)) { + value.cssStyles.push(...style.flatMap((s) => s.split(','))); + } + }); + this.elements.forEach((value) => { + if (value.classes.includes(id)) { + value.cssStyles.push(...style.flatMap((s) => s.split(','))); + } + }); + } + } + + public getClasses() { + return this.classes; + } + + public getData() { + const config = getConfig(); + const nodes: Node[] = []; + const edges: Edge[] = []; + for (const requirement of this.requirements.values()) { + const node = requirement as unknown as Node; + node.id = requirement.name; + node.cssStyles = requirement.cssStyles; + node.cssClasses = requirement.classes.join(' '); + node.shape = 'requirementBox'; + node.look = config.look; + nodes.push(node); + } + + for (const element of this.elements.values()) { + const node = element as unknown as Node; + node.shape = 'requirementBox'; + node.look = config.look; + node.id = element.name; + node.cssStyles = element.cssStyles; + node.cssClasses = element.classes.join(' '); + + nodes.push(node); + } + + for (const relation of this.relations) { + let counter = 0; + const isContains = relation.type === this.Relationships.CONTAINS; + const edge: Edge = { + id: `${relation.src}-${relation.dst}-${counter}`, + start: this.requirements.get(relation.src)?.name ?? this.elements.get(relation.src)?.name, + end: this.requirements.get(relation.dst)?.name ?? this.elements.get(relation.dst)?.name, + label: `<<${relation.type}>>`, + classes: 'relationshipLine', + style: ['fill:none', isContains ? '' : 'stroke-dasharray: 10,7'], + labelpos: 'c', + thickness: 'normal', + type: 'normal', + pattern: isContains ? 'normal' : 'dashed', + arrowTypeEnd: isContains ? 'requirement_contains' : 'requirement_arrow', + look: config.look, + }; + + edges.push(edge); + counter++; + } + + return { nodes, edges, other: {}, config, direction: this.getDirection() }; + } + + public setAccTitle = setAccTitle; + public getAccTitle = getAccTitle; + public setAccDescription = setAccDescription; + public getAccDescription = getAccDescription; + public setDiagramTitle = setDiagramTitle; + public getDiagramTitle = getDiagramTitle; + public getConfig = () => getConfig().class; +} diff --git a/packages/mermaid/src/diagrams/requirement/requirementDiagram.ts b/packages/mermaid/src/diagrams/requirement/requirementDiagram.ts index 619f5b052..246d91197 100644 --- a/packages/mermaid/src/diagrams/requirement/requirementDiagram.ts +++ b/packages/mermaid/src/diagrams/requirement/requirementDiagram.ts @@ -1,13 +1,15 @@ import type { DiagramDefinition } from '../../diagram-api/types.js'; // @ts-ignore: JISON doesn't support types import parser from './parser/requirementDiagram.jison'; -import db from './requirementDb.js'; +import { RequirementDB } from './requirementDb.js'; import styles from './styles.js'; import renderer from './requirementRenderer.js'; export const diagram: DiagramDefinition = { parser, - db, + get db() { + return new RequirementDB(); + }, renderer, styles, };