mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-18 06:49:47 +02:00
Merge branch 'develop' into 6584-piechart-zero-negative-values
This commit is contained in:
@@ -8,6 +8,7 @@ export {
|
||||
Architecture,
|
||||
GitGraph,
|
||||
Radar,
|
||||
TreemapDoc,
|
||||
Branch,
|
||||
Commit,
|
||||
Merge,
|
||||
@@ -19,6 +20,7 @@ export {
|
||||
isPieSection,
|
||||
isArchitecture,
|
||||
isGitGraph,
|
||||
isTreemapDoc,
|
||||
isBranch,
|
||||
isCommit,
|
||||
isMerge,
|
||||
@@ -32,6 +34,7 @@ export {
|
||||
ArchitectureGeneratedModule,
|
||||
GitGraphGeneratedModule,
|
||||
RadarGeneratedModule,
|
||||
TreemapGeneratedModule,
|
||||
} from './generated/module.js';
|
||||
|
||||
export * from './gitGraph/index.js';
|
||||
@@ -41,3 +44,4 @@ export * from './packet/index.js';
|
||||
export * from './pie/index.js';
|
||||
export * from './architecture/index.js';
|
||||
export * from './radar/index.js';
|
||||
export * from './treemap/index.js';
|
||||
|
1
packages/parser/src/language/treemap/index.ts
Normal file
1
packages/parser/src/language/treemap/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './module.js';
|
88
packages/parser/src/language/treemap/module.ts
Normal file
88
packages/parser/src/language/treemap/module.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type {
|
||||
DefaultSharedCoreModuleContext,
|
||||
LangiumCoreServices,
|
||||
LangiumSharedCoreServices,
|
||||
Module,
|
||||
PartialLangiumCoreServices,
|
||||
} from 'langium';
|
||||
import {
|
||||
EmptyFileSystem,
|
||||
createDefaultCoreModule,
|
||||
createDefaultSharedCoreModule,
|
||||
inject,
|
||||
} from 'langium';
|
||||
|
||||
import { MermaidGeneratedSharedModule, TreemapGeneratedModule } from '../generated/module.js';
|
||||
import { TreemapTokenBuilder } from './tokenBuilder.js';
|
||||
import { TreemapValueConverter } from './valueConverter.js';
|
||||
import { TreemapValidator, registerValidationChecks } from './treemap-validator.js';
|
||||
|
||||
/**
|
||||
* Declaration of `Treemap` services.
|
||||
*/
|
||||
interface TreemapAddedServices {
|
||||
parser: {
|
||||
TokenBuilder: TreemapTokenBuilder;
|
||||
ValueConverter: TreemapValueConverter;
|
||||
};
|
||||
validation: {
|
||||
TreemapValidator: TreemapValidator;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Union of Langium default services and `Treemap` services.
|
||||
*/
|
||||
export type TreemapServices = LangiumCoreServices & TreemapAddedServices;
|
||||
|
||||
/**
|
||||
* Dependency injection module that overrides Langium default services and
|
||||
* contributes the declared `Treemap` services.
|
||||
*/
|
||||
export const TreemapModule: Module<
|
||||
TreemapServices,
|
||||
PartialLangiumCoreServices & TreemapAddedServices
|
||||
> = {
|
||||
parser: {
|
||||
TokenBuilder: () => new TreemapTokenBuilder(),
|
||||
ValueConverter: () => new TreemapValueConverter(),
|
||||
},
|
||||
validation: {
|
||||
TreemapValidator: () => new TreemapValidator(),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the full set of services required by Langium.
|
||||
*
|
||||
* First inject the shared services by merging two modules:
|
||||
* - Langium default shared services
|
||||
* - Services generated by langium-cli
|
||||
*
|
||||
* Then inject the language-specific services by merging three modules:
|
||||
* - Langium default language-specific services
|
||||
* - Services generated by langium-cli
|
||||
* - Services specified in this file
|
||||
* @param context - Optional module context with the LSP connection
|
||||
* @returns An object wrapping the shared services and the language-specific services
|
||||
*/
|
||||
export function createTreemapServices(context: DefaultSharedCoreModuleContext = EmptyFileSystem): {
|
||||
shared: LangiumSharedCoreServices;
|
||||
Treemap: TreemapServices;
|
||||
} {
|
||||
const shared: LangiumSharedCoreServices = inject(
|
||||
createDefaultSharedCoreModule(context),
|
||||
MermaidGeneratedSharedModule
|
||||
);
|
||||
const Treemap: TreemapServices = inject(
|
||||
createDefaultCoreModule({ shared }),
|
||||
TreemapGeneratedModule,
|
||||
TreemapModule
|
||||
);
|
||||
shared.ServiceRegistry.register(Treemap);
|
||||
|
||||
// Register validation checks
|
||||
registerValidationChecks(Treemap);
|
||||
|
||||
return { shared, Treemap };
|
||||
}
|
7
packages/parser/src/language/treemap/tokenBuilder.ts
Normal file
7
packages/parser/src/language/treemap/tokenBuilder.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { AbstractMermaidTokenBuilder } from '../common/index.js';
|
||||
|
||||
export class TreemapTokenBuilder extends AbstractMermaidTokenBuilder {
|
||||
public constructor() {
|
||||
super(['treemap']);
|
||||
}
|
||||
}
|
61
packages/parser/src/language/treemap/treemap-validator.ts
Normal file
61
packages/parser/src/language/treemap/treemap-validator.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { ValidationAcceptor, ValidationChecks } from 'langium';
|
||||
import type { MermaidAstType, TreemapDoc } from '../generated/ast.js';
|
||||
import type { TreemapServices } from './module.js';
|
||||
|
||||
/**
|
||||
* Register custom validation checks.
|
||||
*/
|
||||
export function registerValidationChecks(services: TreemapServices) {
|
||||
const validator = services.validation.TreemapValidator;
|
||||
const registry = services.validation.ValidationRegistry;
|
||||
if (registry) {
|
||||
// Use any to bypass type checking since we know TreemapDoc is part of the AST
|
||||
// but the type system is having trouble with it
|
||||
const checks: ValidationChecks<MermaidAstType> = {
|
||||
TreemapDoc: validator.checkSingleRoot.bind(validator),
|
||||
// Remove unused validation for TreemapRow
|
||||
};
|
||||
registry.register(checks, validator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of custom validations.
|
||||
*/
|
||||
export class TreemapValidator {
|
||||
/**
|
||||
* Validates that a treemap has only one root node.
|
||||
* A root node is defined as a node that has no indentation.
|
||||
*/
|
||||
checkSingleRoot(doc: TreemapDoc, accept: ValidationAcceptor): void {
|
||||
let rootNodeIndentation;
|
||||
|
||||
for (const row of doc.TreemapRows) {
|
||||
// Skip non-node items or items without a type
|
||||
if (!row.item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
rootNodeIndentation === undefined && // Check if this is a root node (no indentation)
|
||||
row.indent === undefined
|
||||
) {
|
||||
rootNodeIndentation = 0;
|
||||
} else if (row.indent === undefined) {
|
||||
// If we've already found a root node, report an error
|
||||
accept('error', 'Multiple root nodes are not allowed in a treemap.', {
|
||||
node: row,
|
||||
property: 'item',
|
||||
});
|
||||
} else if (
|
||||
rootNodeIndentation !== undefined &&
|
||||
rootNodeIndentation >= parseInt(row.indent, 10)
|
||||
) {
|
||||
accept('error', 'Multiple root nodes are not allowed in a treemap.', {
|
||||
node: row,
|
||||
property: 'item',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
90
packages/parser/src/language/treemap/treemap.langium
Normal file
90
packages/parser/src/language/treemap/treemap.langium
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Treemap grammar for Langium
|
||||
* Converted from mindmap grammar
|
||||
*
|
||||
* The ML_COMMENT and NL hidden terminals handle whitespace, comments, and newlines
|
||||
* before the treemap keyword, allowing for empty lines and comments before the
|
||||
* treemap declaration.
|
||||
*/
|
||||
grammar Treemap
|
||||
|
||||
|
||||
|
||||
fragment TitleAndAccessibilities:
|
||||
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE))+
|
||||
;
|
||||
|
||||
terminal BOOLEAN returns boolean: 'true' | 'false';
|
||||
|
||||
terminal ACC_DESCR: /[\t ]*accDescr(?:[\t ]*:([^\n\r]*?(?=%%)|[^\n\r]*)|\s*{([^}]*)})/;
|
||||
terminal ACC_TITLE: /[\t ]*accTitle[\t ]*:(?:[^\n\r]*?(?=%%)|[^\n\r]*)/;
|
||||
terminal TITLE: /[\t ]*title(?:[\t ][^\n\r]*?(?=%%)|[\t ][^\n\r]*|)/;
|
||||
|
||||
// Interface declarations for data types
|
||||
interface Item {
|
||||
name: string
|
||||
classSelector?: string // For ::: class
|
||||
}
|
||||
interface Section extends Item {
|
||||
}
|
||||
interface Leaf extends Item {
|
||||
value: number
|
||||
}
|
||||
interface ClassDefStatement {
|
||||
className: string
|
||||
styleText: string // Optional style text
|
||||
}
|
||||
interface TreemapDoc {
|
||||
TreemapRows: TreemapRow[]
|
||||
title?: string
|
||||
accTitle?: string
|
||||
accDescr?: string
|
||||
}
|
||||
|
||||
entry TreemapDoc returns TreemapDoc:
|
||||
TREEMAP_KEYWORD
|
||||
(
|
||||
TitleAndAccessibilities
|
||||
| TreemapRows+=TreemapRow
|
||||
)*;
|
||||
terminal TREEMAP_KEYWORD: 'treemap-beta' | 'treemap';
|
||||
|
||||
terminal CLASS_DEF: /classDef\s+([a-zA-Z_][a-zA-Z0-9_]+)(?:\s+([^;\r\n]*))?(?:;)?/;
|
||||
terminal STYLE_SEPARATOR: ':::';
|
||||
terminal SEPARATOR: ':';
|
||||
terminal COMMA: ',';
|
||||
|
||||
hidden terminal WS: /[ \t]+/; // One or more spaces or tabs for hidden whitespace
|
||||
hidden terminal ML_COMMENT: /\%\%[^\n]*/;
|
||||
hidden terminal NL: /\r?\n/;
|
||||
|
||||
TreemapRow:
|
||||
indent=INDENTATION? (item=Item | ClassDef);
|
||||
|
||||
// Class definition statement handled by the value converter
|
||||
ClassDef returns string:
|
||||
CLASS_DEF;
|
||||
|
||||
Item returns Item:
|
||||
Leaf | Section;
|
||||
|
||||
// Use a special rule order to handle the parsing precedence
|
||||
Section returns Section:
|
||||
name=STRING2 (STYLE_SEPARATOR classSelector=ID2)?;
|
||||
|
||||
Leaf returns Leaf:
|
||||
name=STRING2 INDENTATION? (SEPARATOR | COMMA) INDENTATION? value=MyNumber (STYLE_SEPARATOR classSelector=ID2)?;
|
||||
|
||||
// This should be processed before whitespace is ignored
|
||||
terminal INDENTATION: /[ \t]{1,}/; // One or more spaces/tabs for indentation
|
||||
|
||||
// Keywords with fixed text patterns
|
||||
terminal ID2: /[a-zA-Z_][a-zA-Z0-9_]*/;
|
||||
// Define as a terminal rule
|
||||
terminal NUMBER2: /[0-9_\.\,]+/;
|
||||
|
||||
// Then create a data type rule that uses it
|
||||
MyNumber returns number: NUMBER2;
|
||||
|
||||
terminal STRING2: /"[^"]*"|'[^']*'/;
|
||||
// Modified indentation rule to have higher priority than WS
|
44
packages/parser/src/language/treemap/valueConverter.ts
Normal file
44
packages/parser/src/language/treemap/valueConverter.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { CstNode, GrammarAST, ValueType } from 'langium';
|
||||
import { AbstractMermaidValueConverter } from '../common/index.js';
|
||||
|
||||
// Regular expression to extract className and styleText from a classDef terminal
|
||||
const classDefRegex = /classDef\s+([A-Z_a-z]\w+)(?:\s+([^\n\r;]*))?;?/;
|
||||
|
||||
export class TreemapValueConverter extends AbstractMermaidValueConverter {
|
||||
protected runCustomConverter(
|
||||
rule: GrammarAST.AbstractRule,
|
||||
input: string,
|
||||
_cstNode: CstNode
|
||||
): ValueType | undefined {
|
||||
if (rule.name === 'NUMBER2') {
|
||||
// Convert to a number by removing any commas and converting to float
|
||||
return parseFloat(input.replace(/,/g, ''));
|
||||
} else if (rule.name === 'SEPARATOR') {
|
||||
// Remove quotes
|
||||
return input.substring(1, input.length - 1);
|
||||
} else if (rule.name === 'STRING2') {
|
||||
// Remove quotes
|
||||
return input.substring(1, input.length - 1);
|
||||
} else if (rule.name === 'INDENTATION') {
|
||||
return input.length;
|
||||
} else if (rule.name === 'ClassDef') {
|
||||
// Handle both CLASS_DEF terminal and ClassDef rule
|
||||
if (typeof input !== 'string') {
|
||||
// If we're dealing with an already processed object, return it as is
|
||||
return input;
|
||||
}
|
||||
|
||||
// Extract className and styleText from classDef statement
|
||||
const match = classDefRegex.exec(input);
|
||||
if (match) {
|
||||
// Use any type to avoid type issues
|
||||
return {
|
||||
$type: 'ClassDefStatement',
|
||||
className: match[1],
|
||||
styleText: match[2] || undefined,
|
||||
} as any;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
import type { LangiumParser, ParseResult } from 'langium';
|
||||
|
||||
import type { Info, Packet, Pie, Architecture, GitGraph, Radar } from './index.js';
|
||||
import type { Info, Packet, Pie, Architecture, GitGraph, Radar, Treemap } from './index.js';
|
||||
|
||||
export type DiagramAST = Info | Packet | Pie | Architecture | GitGraph | Radar;
|
||||
|
||||
@@ -36,6 +36,11 @@ const initializers = {
|
||||
const parser = createRadarServices().Radar.parser.LangiumParser;
|
||||
parsers.radar = parser;
|
||||
},
|
||||
treemap: async () => {
|
||||
const { createTreemapServices } = await import('./language/treemap/index.js');
|
||||
const parser = createTreemapServices().Treemap.parser.LangiumParser;
|
||||
parsers.treemap = parser;
|
||||
},
|
||||
} as const;
|
||||
|
||||
export async function parse(diagramType: 'info', text: string): Promise<Info>;
|
||||
@@ -44,6 +49,7 @@ export async function parse(diagramType: 'pie', text: string): Promise<Pie>;
|
||||
export async function parse(diagramType: 'architecture', text: string): Promise<Architecture>;
|
||||
export async function parse(diagramType: 'gitGraph', text: string): Promise<GitGraph>;
|
||||
export async function parse(diagramType: 'radar', text: string): Promise<Radar>;
|
||||
export async function parse(diagramType: 'treemap', text: string): Promise<Treemap>;
|
||||
|
||||
export async function parse<T extends DiagramAST>(
|
||||
diagramType: keyof typeof initializers,
|
||||
|
Reference in New Issue
Block a user