Merge branch 'develop' into 6584-piechart-zero-negative-values

This commit is contained in:
darshanr0107
2025-07-01 12:34:50 +05:30
committed by GitHub
39 changed files with 2917 additions and 867 deletions

View File

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

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

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

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,