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:
Sidharth Vinod
2025-07-10 00:38:41 +05:30
215 changed files with 7951 additions and 2413 deletions

View File

@@ -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';

View File

@@ -12,5 +12,10 @@ entry Packet:
;
PacketBlock:
start=INT('-' end=INT)? ':' label=STRING EOL
(
start=INT('-' end=INT)?
| '+' bits=INT
)
':' label=STRING
EOL
;

View File

@@ -0,0 +1 @@
export * from './module.js';

View 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 };
}

View File

@@ -0,0 +1,7 @@
import { AbstractMermaidTokenBuilder } from '../common/index.js';
export class TreemapTokenBuilder extends AbstractMermaidTokenBuilder {
public constructor() {
super(['treemap']);
}
}

View 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',
});
}
}
}
}

View 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

View 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;
}
}

View File

@@ -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,