diff --git a/.cspell/code-terms.txt b/.cspell/code-terms.txt index 8b549f888..710020c52 100644 --- a/.cspell/code-terms.txt +++ b/.cspell/code-terms.txt @@ -143,6 +143,7 @@ typeof typestr unshift urlsafe +usecase verifymethod VERIFYMTHD WARN_DOCSDIR_DOESNT_MATCH diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index 97b9852ff..fab00bd3c 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -28,6 +28,7 @@ import architecture from '../diagrams/architecture/architectureDetector.js'; import { registerLazyLoadedDiagrams } from './detectType.js'; import { registerDiagram } from './diagramAPI.js'; import { treemap } from '../diagrams/treemap/detector.js'; +import { usecase } from '../diagrams/usecase/detector.js'; import '../type.d.ts'; let hasLoadedDiagrams = false; @@ -101,6 +102,7 @@ export const addDiagrams = () => { xychart, block, radar, - treemap + treemap, + usecase ); }; diff --git a/packages/mermaid/src/diagrams/usecase/db.ts b/packages/mermaid/src/diagrams/usecase/db.ts new file mode 100644 index 000000000..42e8e7135 --- /dev/null +++ b/packages/mermaid/src/diagrams/usecase/db.ts @@ -0,0 +1,38 @@ +import type { DiagramDB } from '../../diagram-api/types.js'; +import type { UsecaseDiagramConfig, UsecaseNode } from './types.js'; +import { cleanAndMerge } from '../../utils.js'; + +export class UsecaseDiagramDB implements DiagramDB { + public getNodes() { + return []; + } + + public getConfig() { + return cleanAndMerge({}) as Required; + } + + public addNode(node: UsecaseNode, level: number) { + if (level === 0) { + // TODO + } + } + + public getRoot() { + return { name: '', children: [] }; + } + + public addClass(_id: string, _style: string) { + // TODO + } + public getClasses() { + // TODO + } + + public getStylesForClass(_classSelector: string) { + // TODO + } + + public clear() { + // commonClear(); + } +} diff --git a/packages/mermaid/src/diagrams/usecase/detector.ts b/packages/mermaid/src/diagrams/usecase/detector.ts new file mode 100644 index 000000000..71b58bef6 --- /dev/null +++ b/packages/mermaid/src/diagrams/usecase/detector.ts @@ -0,0 +1,22 @@ +import type { + DiagramDetector, + DiagramLoader, + ExternalDiagramDefinition, +} from '../../diagram-api/types.js'; + +const id = 'usecase'; + +const detector: DiagramDetector = (txt) => { + return /^\s*usecase/.test(txt); +}; + +const loader: DiagramLoader = async () => { + const { diagram } = await import('./diagram.js'); + return { id, diagram }; +}; + +export const usecase: ExternalDiagramDefinition = { + id, + detector, + loader, +}; diff --git a/packages/mermaid/src/diagrams/usecase/diagram.ts b/packages/mermaid/src/diagrams/usecase/diagram.ts new file mode 100644 index 000000000..507273e36 --- /dev/null +++ b/packages/mermaid/src/diagrams/usecase/diagram.ts @@ -0,0 +1,14 @@ +import type { DiagramDefinition } from '../../diagram-api/types.js'; +import { UsecaseDiagramDB } from './db.js'; +import { parser } from './parser.js'; +import { renderer } from './renderer.js'; +import styles from './styles.js'; + +export const diagram: DiagramDefinition = { + parser, + get db() { + return new UsecaseDiagramDB(); + }, + renderer, + styles, +}; diff --git a/packages/mermaid/src/diagrams/usecase/parser.ts b/packages/mermaid/src/diagrams/usecase/parser.ts new file mode 100644 index 000000000..e679c8ae7 --- /dev/null +++ b/packages/mermaid/src/diagrams/usecase/parser.ts @@ -0,0 +1,40 @@ +import { parse } from '@mermaid-js/parser'; +import type { ParserDefinition } from '../../diagram-api/types.js'; +import { log } from '../../logger.js'; +import { populateCommonDb } from '../common/populateCommonDb.js'; +import type { UsecaseAst } from './types.js'; +import { UsecaseDiagramDB } from './db.js'; + +/** + * Populates the database with data from the Usecase AST + * @param ast - The Usecase AST + */ +const populate = (ast: UsecaseAst, db: UsecaseDiagramDB) => { + // We need to bypass the type checking for populateCommonDb + // eslint-disable-next-line @typescript-eslint/no-explicit-any + populateCommonDb(ast as any, db); +}; + +export const parser: ParserDefinition = { + // @ts-expect-error - UsecaseDB is not assignable to DiagramDB + parser: { yy: undefined }, + parse: async (text: string): Promise => { + try { + // Use a generic parse that accepts any diagram type + + const parseFunc = parse as (diagramType: string, text: string) => Promise; + const ast = await parseFunc('usecase', text); + log.debug('Usecase AST:', ast); + const db = parser.parser?.yy; + if (!(db instanceof UsecaseDiagramDB)) { + throw new Error( + 'parser.parser?.yy was not a UsecaseDiagramDB. This is due to a bug within Mermaid, please report this issue at https://github.com/mermaid-js/mermaid/issues.' + ); + } + populate(ast, db); + } catch (error) { + log.error('Error parsing usecase:', error); + throw error; + } + }, +}; diff --git a/packages/mermaid/src/diagrams/usecase/renderer.ts b/packages/mermaid/src/diagrams/usecase/renderer.ts new file mode 100644 index 000000000..6f6b5305f --- /dev/null +++ b/packages/mermaid/src/diagrams/usecase/renderer.ts @@ -0,0 +1,21 @@ +import type { Diagram } from '../../Diagram.js'; +import type { + DiagramRenderer, + DiagramStyleClassDef, + DrawDefinition, +} from '../../diagram-api/types.js'; + +/** + * Draws the Usecase diagram + */ +const draw: DrawDefinition = (_text, _id, _version, _diagram: Diagram) => { + // TODO: Implement the draw function for the usecase diagram +}; + +const getClasses = function ( + _text: string, + _diagramObj: Pick +): Map { + return new Map(); +}; +export const renderer: DiagramRenderer = { draw, getClasses }; diff --git a/packages/mermaid/src/diagrams/usecase/styles.ts b/packages/mermaid/src/diagrams/usecase/styles.ts new file mode 100644 index 000000000..9593a6296 --- /dev/null +++ b/packages/mermaid/src/diagrams/usecase/styles.ts @@ -0,0 +1,51 @@ +import type { DiagramStylesProvider } from '../../diagram-api/types.js'; +import { cleanAndMerge } from '../../utils.js'; +import type { UsecaseStyleOptions } from './types.js'; + +const defaultUsecaseStyleOptions: UsecaseStyleOptions = { + sectionStrokeColor: 'black', + sectionStrokeWidth: '1', + sectionFillColor: '#efefef', + leafStrokeColor: 'black', + leafStrokeWidth: '1', + leafFillColor: '#efefef', + labelColor: 'black', + labelFontSize: '12px', + valueFontSize: '10px', + valueColor: 'black', + titleColor: 'black', + titleFontSize: '14px', +}; + +export const getStyles: DiagramStylesProvider = ({ + usecase, +}: { usecase?: UsecaseStyleOptions } = {}) => { + const options = cleanAndMerge(defaultUsecaseStyleOptions, usecase); + + return ` + .usecaseNode.section { + stroke: ${options.sectionStrokeColor}; + stroke-width: ${options.sectionStrokeWidth}; + fill: ${options.sectionFillColor}; + } + .usecaseNode.leaf { + stroke: ${options.leafStrokeColor}; + stroke-width: ${options.leafStrokeWidth}; + fill: ${options.leafFillColor}; + } + .usecaseLabel { + fill: ${options.labelColor}; + font-size: ${options.labelFontSize}; + } + .usecaseValue { + fill: ${options.valueColor}; + font-size: ${options.valueFontSize}; + } + .usecaseTitle { + fill: ${options.titleColor}; + font-size: ${options.titleFontSize}; + } + `; +}; + +export default getStyles; diff --git a/packages/mermaid/src/diagrams/usecase/types.ts b/packages/mermaid/src/diagrams/usecase/types.ts new file mode 100644 index 000000000..977dca8d5 --- /dev/null +++ b/packages/mermaid/src/diagrams/usecase/types.ts @@ -0,0 +1,68 @@ +import type { DiagramDBBase, DiagramStyleClassDef } from '../../diagram-api/types.js'; +import type { BaseDiagramConfig } from '../../config.type.js'; + +export interface UsecaseNode { + name: string; + children?: UsecaseNode[]; + value?: number; + parent?: UsecaseNode; + classSelector?: string; + cssCompiledStyles?: string[]; +} + +export interface UsecaseDiagramDB extends DiagramDBBase { + getNodes: () => UsecaseNode[]; + addNode: (node: UsecaseNode, level: number) => void; + getRoot: () => UsecaseNode | undefined; + getClasses: () => Map; + addClass: (className: string, style: string) => void; + getStylesForClass: (classSelector: string) => string[]; + // Update +} + +export interface UsecaseStyleOptions { + sectionStrokeColor?: string; + sectionStrokeWidth?: string; + sectionFillColor?: string; + leafStrokeColor?: string; + leafStrokeWidth?: string; + leafFillColor?: string; + labelColor?: string; + labelFontSize?: string; + valueFontSize?: string; + valueColor?: string; + titleColor?: string; + titleFontSize?: string; +} + +export interface UsecaseData { + nodes: UsecaseNode[]; + levels: Map; + root?: UsecaseNode; + outerNodes: UsecaseNode[]; +} + +export interface UsecaseItem { + $type: string; + name: string; + value?: number; + classSelector?: string; +} + +export interface UsecaseAst { + title?: string; + description?: string; +} + +// Define the UsecaseDiagramConfig interface +export interface UsecaseDiagramConfig extends BaseDiagramConfig { + padding?: number; + diagramPadding?: number; + showValues?: boolean; + nodeWidth?: number; + nodeHeight?: number; + borderWidth?: number; + valueFontSize?: number; + labelFontSize?: number; + valueFormat?: string; +} diff --git a/packages/parser/langium-config.json b/packages/parser/langium-config.json index 85c722a02..cec3cc2cd 100644 --- a/packages/parser/langium-config.json +++ b/packages/parser/langium-config.json @@ -35,6 +35,11 @@ "id": "treemap", "grammar": "src/language/treemap/treemap.langium", "fileExtensions": [".mmd", ".mermaid"] + }, + { + "id": "usecase", + "grammar": "src/language/usecase/usecase.langium", + "fileExtensions": [".mmd", ".mermaid"] } ], "mode": "production", diff --git a/packages/parser/src/language/index.ts b/packages/parser/src/language/index.ts index 9b31af5ba..89bfc15da 100644 --- a/packages/parser/src/language/index.ts +++ b/packages/parser/src/language/index.ts @@ -9,6 +9,7 @@ export { GitGraph, Radar, Treemap, + Usecase, Branch, Commit, Merge, @@ -24,6 +25,7 @@ export { isBranch, isCommit, isMerge, + isUsecase, } from './generated/ast.js'; export { @@ -35,6 +37,7 @@ export { GitGraphGeneratedModule, RadarGeneratedModule, TreemapGeneratedModule, + UsecaseGeneratedModule, } from './generated/module.js'; export * from './gitGraph/index.js'; @@ -45,3 +48,4 @@ export * from './pie/index.js'; export * from './architecture/index.js'; export * from './radar/index.js'; export * from './treemap/index.js'; +export * from './usecase/index.js'; diff --git a/packages/parser/src/language/usecase/index.ts b/packages/parser/src/language/usecase/index.ts new file mode 100644 index 000000000..fd3c604b0 --- /dev/null +++ b/packages/parser/src/language/usecase/index.ts @@ -0,0 +1 @@ +export * from './module.js'; diff --git a/packages/parser/src/language/usecase/module.ts b/packages/parser/src/language/usecase/module.ts new file mode 100644 index 000000000..156eac759 --- /dev/null +++ b/packages/parser/src/language/usecase/module.ts @@ -0,0 +1,35 @@ +import { type DefaultSharedCoreModuleContext, type LangiumCoreServices } from 'langium'; +import type { Module, PartialLangiumCoreServices } from 'langium'; +import { EmptyFileSystem } from 'langium'; +import { UsecaseTokenBuilder } from './tokenBuilder.js'; +import { UsecaseValueConverter } from './valueConverter.js'; +import { UsecaseValidator } from './usecase-validator.js'; + +interface UsecaseAddedServices { + parser: { + TokenBuilder: UsecaseTokenBuilder; + ValueConverter: UsecaseValueConverter; + }; + validation: { + UsecaseValidator: UsecaseValidator; + }; +} + +export type UsecaseServices = LangiumCoreServices & UsecaseAddedServices; + +export const UsecaseModule: Module< + UsecaseServices, + PartialLangiumCoreServices & UsecaseAddedServices +> = { + parser: { + TokenBuilder: () => new UsecaseTokenBuilder(), + ValueConverter: () => new UsecaseValueConverter(), + }, + validation: { + UsecaseValidator: () => new UsecaseValidator(), + }, +}; + +export function createUsecaseServices(_context: DefaultSharedCoreModuleContext = EmptyFileSystem) { + // TODO +} diff --git a/packages/parser/src/language/usecase/tokenBuilder.ts b/packages/parser/src/language/usecase/tokenBuilder.ts new file mode 100644 index 000000000..cf861374b --- /dev/null +++ b/packages/parser/src/language/usecase/tokenBuilder.ts @@ -0,0 +1,7 @@ +import { AbstractMermaidTokenBuilder } from '../common/index.js'; + +export class UsecaseTokenBuilder extends AbstractMermaidTokenBuilder { + public constructor() { + super(['usecase']); + } +} diff --git a/packages/parser/src/language/usecase/usecase-validator.ts b/packages/parser/src/language/usecase/usecase-validator.ts new file mode 100644 index 000000000..5d14b46c2 --- /dev/null +++ b/packages/parser/src/language/usecase/usecase-validator.ts @@ -0,0 +1,12 @@ +import type { UsecaseServices } from './module.js'; +/** + * Register custom validation checks. + */ +export function registerValidationChecks(_services: UsecaseServices) { + // TODO +} + +/** + * Implementation of custom validations. + */ +export class UsecaseValidator {} diff --git a/packages/parser/src/language/usecase/usecase.langium b/packages/parser/src/language/usecase/usecase.langium new file mode 100644 index 000000000..0ffdd02fc --- /dev/null +++ b/packages/parser/src/language/usecase/usecase.langium @@ -0,0 +1 @@ +// TODO \ No newline at end of file diff --git a/packages/parser/src/language/usecase/valueConverter.ts b/packages/parser/src/language/usecase/valueConverter.ts new file mode 100644 index 000000000..af5ab2af4 --- /dev/null +++ b/packages/parser/src/language/usecase/valueConverter.ts @@ -0,0 +1,12 @@ +import type { CstNode, GrammarAST, ValueType } from 'langium'; +import { AbstractMermaidValueConverter } from '../common/index.js'; + +export class UsecaseValueConverter extends AbstractMermaidValueConverter { + protected runCustomConverter( + _rule: GrammarAST.AbstractRule, + _input: string, + _cstNode: CstNode + ): ValueType | undefined { + return undefined; + } +} diff --git a/packages/parser/src/parse.ts b/packages/parser/src/parse.ts index 6f5a94ce6..53d970e9b 100644 --- a/packages/parser/src/parse.ts +++ b/packages/parser/src/parse.ts @@ -1,6 +1,15 @@ import type { LangiumParser, ParseResult } from 'langium'; -import type { Info, Packet, Pie, Architecture, GitGraph, Radar, Treemap } from './index.js'; +import type { + Info, + Packet, + Pie, + Architecture, + GitGraph, + Radar, + Treemap, + // Usecase, +} from './index.js'; export type DiagramAST = Info | Packet | Pie | Architecture | GitGraph | Radar; @@ -41,6 +50,11 @@ const initializers = { const parser = createTreemapServices().Treemap.parser.LangiumParser; parsers.treemap = parser; }, + // usecase: async () => { + // const { createUsecaseServices } = await import('./language/usecase/index.js'); + // const parser = createUsecaseServices().Usecase.parser.LangiumParser; + // parsers.usecase = parser; + // }, } as const; export async function parse(diagramType: 'info', text: string): Promise; @@ -50,6 +64,7 @@ export async function parse(diagramType: 'architecture', text: string): Promise< export async function parse(diagramType: 'gitGraph', text: string): Promise; export async function parse(diagramType: 'radar', text: string): Promise; export async function parse(diagramType: 'treemap', text: string): Promise; +// export async function parse(diagramType: 'usecase', text: string): Promise; export async function parse( diagramType: keyof typeof initializers,