mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-17 14:29:48 +02:00
Merge branch 'develop' into sidv/packet-release
* develop: (266 commits) chore: update E2E timings docs: Update changeset fix: Add useful error message to test chore: Add treemap example chore: Add treemap example chore: Add peerDependency to @mermaid-js/examples Version Packages docs: Update changeset update in changeset updated validaor and tests to use treemap Updated parser to use treemap chore: Fail build in CI on type errors fix text going outside node Version Packages Updated phpbb url to support 403 in lychee Updated lychee for working url phpbb.com resolve PR comment resolve PR comments chore: update E2E timings add changeset ...
This commit is contained in:
@@ -8,6 +8,7 @@ export {
|
||||
Architecture,
|
||||
GitGraph,
|
||||
Radar,
|
||||
Treemap,
|
||||
Branch,
|
||||
Commit,
|
||||
Merge,
|
||||
@@ -19,6 +20,7 @@ export {
|
||||
isPieSection,
|
||||
isArchitecture,
|
||||
isGitGraph,
|
||||
isTreemap,
|
||||
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';
|
||||
|
@@ -12,5 +12,10 @@ entry Packet:
|
||||
;
|
||||
|
||||
PacketBlock:
|
||||
start=INT('-' end=INT)? ':' label=STRING EOL
|
||||
(
|
||||
start=INT('-' end=INT)?
|
||||
| '+' bits=INT
|
||||
)
|
||||
':' label=STRING
|
||||
EOL
|
||||
;
|
||||
|
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, Treemap } 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 Treemap is part of the AST
|
||||
// but the type system is having trouble with it
|
||||
const checks: ValidationChecks<MermaidAstType> = {
|
||||
Treemap: 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: Treemap, 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 Treemap {
|
||||
TreemapRows: TreemapRow[]
|
||||
title?: string
|
||||
accTitle?: string
|
||||
accDescr?: string
|
||||
}
|
||||
|
||||
entry Treemap returns Treemap:
|
||||
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