Merge pull request #6407 from thomascizeron/fix/6162_greedyRegexInCommonLangium

Fix: greedy regex in common langium
This commit is contained in:
Sidharth Vinod
2025-04-08 11:33:43 +00:00
committed by GitHub
21 changed files with 470 additions and 294 deletions

View File

@@ -0,0 +1,6 @@
---
'mermaid': patch
'@mermaid-js/parser': patch
---
Refactor grammar so that title don't break Architecture Diagrams

View File

@@ -19,6 +19,25 @@ describe.skip('architecture diagram', () => {
` `
); );
}); });
it('should render a simple architecture diagram with titleAndAccessabilities', () => {
imgSnapshotTest(
`architecture-beta
title Simple Architecture Diagram
accTitle: Accessibility Title
accDescr: Accessibility Description
group api(cloud)[API]
service db(database)[Database] in api
service disk1(disk)[Storage] in api
service disk2(disk)[Storage] in api
service server(server)[Server] in api
db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db
`
);
});
it('should render an architecture diagram with groups within groups', () => { it('should render an architecture diagram with groups within groups', () => {
imgSnapshotTest( imgSnapshotTest(
`architecture-beta `architecture-beta
@@ -172,7 +191,7 @@ describe.skip('architecture diagram', () => {
); );
}); });
it('should render an architecture diagram with a resonable height', () => { it('should render an architecture diagram with a reasonable height', () => {
imgSnapshotTest( imgSnapshotTest(
`architecture-beta `architecture-beta
group federated(cloud)[Federated Environment] group federated(cloud)[Federated Environment]

View File

@@ -0,0 +1,70 @@
import { it, describe, expect } from 'vitest';
import { db } from './architectureDb.js';
import { parser } from './architectureParser.js';
const {
clear,
getDiagramTitle,
getAccTitle,
getAccDescription,
getServices,
getGroups,
getEdges,
getJunctions,
} = db;
describe('architecture diagrams', () => {
beforeEach(() => {
clear();
});
describe('architecture diagram definitions', () => {
it('should handle the architecture keyword', async () => {
const str = `architecture-beta`;
await expect(parser.parse(str)).resolves.not.toThrow();
});
it('should handle an simple radar definition', async () => {
const str = `architecture-beta
service db
`;
await expect(parser.parse(str)).resolves.not.toThrow();
});
});
describe('should handle TitleAndAccessibilities', () => {
it('should handle title on the first line', async () => {
const str = `architecture-beta title Simple Architecture Diagram`;
await expect(parser.parse(str)).resolves.not.toThrow();
expect(getDiagramTitle()).toBe('Simple Architecture Diagram');
});
it('should handle title on another line', async () => {
const str = `architecture-beta
title Simple Architecture Diagram
`;
await expect(parser.parse(str)).resolves.not.toThrow();
expect(getDiagramTitle()).toBe('Simple Architecture Diagram');
});
it('should handle accessibility title and description', async () => {
const str = `architecture-beta
accTitle: Accessibility Title
accDescr: Accessibility Description
`;
await expect(parser.parse(str)).resolves.not.toThrow();
expect(getAccTitle()).toBe('Accessibility Title');
expect(getAccDescription()).toBe('Accessibility Description');
});
it('should handle multiline accessibility description', async () => {
const str = `architecture-beta
accDescr {
Accessibility Description
}
`;
await expect(parser.parse(str)).resolves.not.toThrow();
expect(getAccDescription()).toBe('Accessibility Description');
});
});
});

View File

@@ -1,6 +1,6 @@
import type { ArchitectureDiagramConfig } from '../../config.type.js'; import type { ArchitectureDiagramConfig } from '../../config.type.js';
import DEFAULT_CONFIG from '../../defaultConfig.js'; import DEFAULT_CONFIG from '../../defaultConfig.js';
import { getConfig } from '../../diagram-api/diagramAPI.js'; import { getConfig as commonGetConfig } from '../../config.js';
import type { D3Element } from '../../types.js'; import type { D3Element } from '../../types.js';
import { ImperativeState } from '../../utils/imperativeState.js'; import { ImperativeState } from '../../utils/imperativeState.js';
import { import {
@@ -33,6 +33,7 @@ import {
isArchitectureService, isArchitectureService,
shiftPositionByArchitectureDirectionPair, shiftPositionByArchitectureDirectionPair,
} from './architectureTypes.js'; } from './architectureTypes.js';
import { cleanAndMerge } from '../../utils.js';
const DEFAULT_ARCHITECTURE_CONFIG: Required<ArchitectureDiagramConfig> = const DEFAULT_ARCHITECTURE_CONFIG: Required<ArchitectureDiagramConfig> =
DEFAULT_CONFIG.architecture; DEFAULT_CONFIG.architecture;
@@ -316,6 +317,14 @@ const setElementForId = (id: string, element: D3Element) => {
}; };
const getElementById = (id: string) => state.records.elements[id]; const getElementById = (id: string) => state.records.elements[id];
const getConfig = (): Required<ArchitectureDiagramConfig> => {
const config = cleanAndMerge({
...DEFAULT_ARCHITECTURE_CONFIG,
...commonGetConfig().architecture,
});
return config;
};
export const db: ArchitectureDB = { export const db: ArchitectureDB = {
clear, clear,
setDiagramTitle, setDiagramTitle,
@@ -324,6 +333,7 @@ export const db: ArchitectureDB = {
getAccTitle, getAccTitle,
setAccDescription, setAccDescription,
getAccDescription, getAccDescription,
getConfig,
addService, addService,
getServices, getServices,
@@ -348,9 +358,5 @@ export const db: ArchitectureDB = {
export function getConfigField<T extends keyof ArchitectureDiagramConfig>( export function getConfigField<T extends keyof ArchitectureDiagramConfig>(
field: T field: T
): Required<ArchitectureDiagramConfig>[T] { ): Required<ArchitectureDiagramConfig>[T] {
const arch = getConfig().architecture; return getConfig()[field];
if (arch?.[field]) {
return arch[field] as Required<ArchitectureDiagramConfig>[T];
}
return DEFAULT_ARCHITECTURE_CONFIG[field];
} }

View File

@@ -500,6 +500,8 @@ function layoutArchitecture(
} }
export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram) => { export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram) => {
// TODO: Add title support for architecture diagrams
const db = diagObj.db as ArchitectureDB; const db = diagObj.db as ArchitectureDB;
const services = db.getServices(); const services = db.getServices();

View File

@@ -1,4 +1,4 @@
import type { DiagramDB } from '../../diagram-api/types.js'; import type { DiagramDBBase } from '../../diagram-api/types.js';
import type { ArchitectureDiagramConfig } from '../../config.type.js'; import type { ArchitectureDiagramConfig } from '../../config.type.js';
import type { D3Element } from '../../types.js'; import type { D3Element } from '../../types.js';
import type cytoscape from 'cytoscape'; import type cytoscape from 'cytoscape';
@@ -242,7 +242,7 @@ export interface ArchitectureEdge<DT = ArchitectureDirection> {
title?: string; title?: string;
} }
export interface ArchitectureDB extends DiagramDB { export interface ArchitectureDB extends DiagramDBBase<ArchitectureDiagramConfig> {
clear: () => void; clear: () => void;
addService: (service: Omit<ArchitectureService, 'edges'>) => void; addService: (service: Omit<ArchitectureService, 'edges'>) => void;
getServices: () => ArchitectureService[]; getServices: () => ArchitectureService[];

View File

@@ -1,11 +1,9 @@
import { it, describe, expect } from 'vitest'; import { it, describe, expect } from 'vitest';
import { db } from './db.js'; import { db } from './db.js';
import { parser } from './parser.js'; import { parser } from './parser.js';
import { renderer, relativeRadius, closedRoundCurve } from './renderer.js'; import { relativeRadius, closedRoundCurve } from './renderer.js';
import { Diagram } from '../../Diagram.js'; import { Diagram } from '../../Diagram.js';
import mermaidAPI from '../../mermaidAPI.js'; import mermaidAPI from '../../mermaidAPI.js';
import { a } from 'vitest/dist/chunks/suite.qtkXWc6R.js';
import { buildRadarStyleOptions } from './styles.js';
const { const {
clear, clear,

View File

@@ -31,6 +31,7 @@ vi.mock('./diagrams/xychart/xychartRenderer.js');
vi.mock('./diagrams/requirement/requirementRenderer.js'); vi.mock('./diagrams/requirement/requirementRenderer.js');
vi.mock('./diagrams/sequence/sequenceRenderer.js'); vi.mock('./diagrams/sequence/sequenceRenderer.js');
vi.mock('./diagrams/radar/renderer.js'); vi.mock('./diagrams/radar/renderer.js');
vi.mock('./diagrams/architecture/architectureRenderer.js');
// ------------------------------------- // -------------------------------------
@@ -799,6 +800,7 @@ graph TD;A--x|text including URL space|B;`)
{ textDiagramType: 'sequenceDiagram', expectedType: 'sequence' }, { textDiagramType: 'sequenceDiagram', expectedType: 'sequence' },
{ textDiagramType: 'stateDiagram-v2', expectedType: 'stateDiagram' }, { textDiagramType: 'stateDiagram-v2', expectedType: 'stateDiagram' },
{ textDiagramType: 'radar-beta', expectedType: 'radar' }, { textDiagramType: 'radar-beta', expectedType: 'radar' },
{ textDiagramType: 'architecture-beta', expectedType: 'architecture' },
]; ];
describe('accessibility', () => { describe('accessibility', () => {

View File

@@ -0,0 +1,2 @@
terminal ARCH_ICON: /\([\w-:]+\)/;
terminal ARCH_TITLE: /\[[\w ]+\]/;

View File

@@ -1,14 +1,15 @@
grammar Architecture grammar Architecture
import "../common/common"; import "../common/common";
import "arch";
entry Architecture: entry Architecture:
NEWLINE* NEWLINE*
"architecture-beta" "architecture-beta"
( (
NEWLINE* TitleAndAccessibilities NEWLINE
| NEWLINE* Statement* | TitleAndAccessibilities
| NEWLINE* | Statement
) )*
; ;
fragment Statement: fragment Statement:
@@ -31,25 +32,21 @@ fragment Arrow:
; ;
Group: Group:
'group' id=ARCH_ID icon=ARCH_ICON? title=ARCH_TITLE? ('in' in=ARCH_ID)? EOL 'group' id=ID icon=ARCH_ICON? title=ARCH_TITLE? ('in' in=ID)? EOL
; ;
Service: Service:
'service' id=ARCH_ID (iconText=ARCH_TEXT_ICON | icon=ARCH_ICON)? title=ARCH_TITLE? ('in' in=ARCH_ID)? EOL 'service' id=ID (iconText=STRING | icon=ARCH_ICON)? title=ARCH_TITLE? ('in' in=ID)? EOL
; ;
Junction: Junction:
'junction' id=ARCH_ID ('in' in=ARCH_ID)? EOL 'junction' id=ID ('in' in=ID)? EOL
; ;
Edge: Edge:
lhsId=ARCH_ID lhsGroup?=ARROW_GROUP? Arrow rhsId=ARCH_ID rhsGroup?=ARROW_GROUP? EOL lhsId=ID lhsGroup?=ARROW_GROUP? Arrow rhsId=ID rhsGroup?=ARROW_GROUP? EOL
; ;
terminal ARROW_DIRECTION: 'L' | 'R' | 'T' | 'B'; terminal ARROW_DIRECTION: 'L' | 'R' | 'T' | 'B';
terminal ARCH_ID: /[\w]+/;
terminal ARCH_TEXT_ICON: /\("[^"]+"\)/;
terminal ARCH_ICON: /\([\w-:]+\)/;
terminal ARCH_TITLE: /\[[\w ]+\]/;
terminal ARROW_GROUP: /\{group\}/; terminal ARROW_GROUP: /\{group\}/;
terminal ARROW_INTO: /<|>/; terminal ARROW_INTO: /<|>/;

View File

@@ -1,22 +1,35 @@
interface Common { // Base terminals and fragments for common language constructs
accDescr?: string; // Terminal Precedence: Lazy to Greedy
accTitle?: string; // When imported, the terminals are considered after the terminals in the importing grammar
title?: string; // Note: Hence, to add a terminal greedier than the common terminals, import it separately after the common import
}
fragment TitleAndAccessibilities:
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) EOL)+
;
fragment EOL returns string: fragment EOL returns string:
NEWLINE+ | EOF NEWLINE+ | EOF
; ;
terminal NEWLINE: /\r?\n/; fragment TitleAndAccessibilities:
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) EOL)+
;
terminal BOOLEAN returns boolean: 'true' | 'false';
terminal ACC_DESCR: /[\t ]*accDescr(?:[\t ]*:([^\n\r]*?(?=%%)|[^\n\r]*)|\s*{([^}]*)})/; terminal ACC_DESCR: /[\t ]*accDescr(?:[\t ]*:([^\n\r]*?(?=%%)|[^\n\r]*)|\s*{([^}]*)})/;
terminal ACC_TITLE: /[\t ]*accTitle[\t ]*:(?:[^\n\r]*?(?=%%)|[^\n\r]*)/; terminal ACC_TITLE: /[\t ]*accTitle[\t ]*:(?:[^\n\r]*?(?=%%)|[^\n\r]*)/;
terminal TITLE: /[\t ]*title(?:[\t ][^\n\r]*?(?=%%)|[\t ][^\n\r]*|)/; terminal TITLE: /[\t ]*title(?:[\t ][^\n\r]*?(?=%%)|[\t ][^\n\r]*|)/;
terminal FLOAT returns number: /[0-9]+\.[0-9]+(?!\.)/;
terminal INT returns number: /0|[1-9][0-9]*(?!\.)/;
terminal NUMBER returns number: FLOAT | INT;
terminal STRING returns string: /"([^"\\]|\\.)*"|'([^'\\]|\\.)*'/;
// Alphanumerics with underscores and dashes
// Must start with an alphanumeric or an underscore
// Cant end with a dash
terminal ID returns string: /[\w]([-\w]*\w)?/;
terminal NEWLINE: /\r?\n/;
hidden terminal WHITESPACE: /[\t ]+/; hidden terminal WHITESPACE: /[\t ]+/;
hidden terminal YAML: /---[\t ]*\r?\n(?:[\S\s]*?\r?\n)?---(?:\r?\n|(?!\S))/; hidden terminal YAML: /---[\t ]*\r?\n(?:[\S\s]*?\r?\n)?---(?:\r?\n|(?!\S))/;
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%(?:\r?\n|(?!\S))/; hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%(?:\r?\n|(?!\S))/;

View File

@@ -1,39 +1,15 @@
grammar GitGraph grammar GitGraph
import "../common/common";
interface Common { import "reference";
accDescr?: string;
accTitle?: string;
title?: string;
}
fragment TitleAndAccessibilities:
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) EOL)+
;
fragment EOL returns string:
NEWLINE+ | EOF
;
terminal NEWLINE: /\r?\n/;
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]*|)/;
hidden terminal WHITESPACE: /[\t ]+/;
hidden terminal YAML: /---[\t ]*\r?\n(?:[\S\s]*?\r?\n)?---(?:\r?\n|(?!\S))/;
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%(?:\r?\n|(?!\S))/;
hidden terminal SINGLE_LINE_COMMENT: /[\t ]*%%[^\n\r]*/;
entry GitGraph: entry GitGraph:
NEWLINE* NEWLINE*
('gitGraph' | 'gitGraph' ':' | 'gitGraph:' | ('gitGraph' Direction ':')) ('gitGraph' | 'gitGraph' ':' | 'gitGraph:' | ('gitGraph' Direction ':'))
NEWLINE*
( (
NEWLINE* NEWLINE
(TitleAndAccessibilities | | TitleAndAccessibilities
statements+=Statement | | statements+=Statement
NEWLINE)* )*
)
; ;
Statement Statement
@@ -56,12 +32,12 @@ Commit:
|'type:' type=('NORMAL' | 'REVERSE' | 'HIGHLIGHT') |'type:' type=('NORMAL' | 'REVERSE' | 'HIGHLIGHT')
)* EOL; )* EOL;
Branch: Branch:
'branch' name=(ID|STRING) 'branch' name=(REFERENCE|STRING)
('order:' order=INT)? ('order:' order=INT)?
EOL; EOL;
Merge: Merge:
'merge' branch=(ID|STRING) 'merge' branch=(REFERENCE|STRING)
( (
'id:' id=STRING 'id:' id=STRING
|'tag:' tags+=STRING |'tag:' tags+=STRING
@@ -69,7 +45,7 @@ Merge:
)* EOL; )* EOL;
Checkout: Checkout:
('checkout'|'switch') branch=(ID|STRING) EOL; ('checkout'|'switch') branch=(REFERENCE|STRING) EOL;
CherryPicking: CherryPicking:
'cherry-pick' 'cherry-pick'
@@ -78,10 +54,3 @@ CherryPicking:
|'tag:' tags+=STRING |'tag:' tags+=STRING
|'parent:' parent=STRING |'parent:' parent=STRING
)* EOL; )* EOL;
terminal INT returns number: /[0-9]+(?=\s)/;
terminal ID returns string: /\w([-\./\w]*[-\w])?/;
terminal STRING: /"[^"]*"|'[^']*'/;

View File

@@ -0,0 +1,4 @@
// Alphanumerics with underscores, dashes, slashes, and dots
// Must start with an alphanumeric or an underscore
// Cant end with a dash, slash, or dot
terminal REFERENCE returns string: /\w([-\./\w]*[-\w])?/;

View File

@@ -12,7 +12,6 @@ export {
Commit, Commit,
Merge, Merge,
Statement, Statement,
isCommon,
isInfo, isInfo,
isPacket, isPacket,
isPacketBlock, isPacketBlock,

View File

@@ -5,15 +5,12 @@ entry Packet:
NEWLINE* NEWLINE*
"packet-beta" "packet-beta"
( (
NEWLINE* TitleAndAccessibilities blocks+=PacketBlock* TitleAndAccessibilities
| NEWLINE+ blocks+=PacketBlock+ | blocks+=PacketBlock
| NEWLINE* | NEWLINE
) )*
; ;
PacketBlock: PacketBlock:
start=INT('-' end=INT)? ':' label=STRING EOL start=INT('-' end=INT)? ':' label=STRING EOL
; ;
terminal INT returns number: /0|[1-9][0-9]*/;
terminal STRING: /"[^"]*"|'[^']*'/;

View File

@@ -5,15 +5,12 @@ entry Pie:
NEWLINE* NEWLINE*
"pie" showData?="showData"? "pie" showData?="showData"?
( (
NEWLINE* TitleAndAccessibilities sections+=PieSection* TitleAndAccessibilities
| NEWLINE+ sections+=PieSection+ | sections+=PieSection
| NEWLINE* | NEWLINE
) )*
; ;
PieSection: PieSection:
label=PIE_SECTION_LABEL ":" value=PIE_SECTION_VALUE EOL label=STRING ":" value=NUMBER EOL
; ;
terminal PIE_SECTION_LABEL: /"[^"]+"/;
terminal PIE_SECTION_VALUE returns number: /(0|[1-9][0-9]*)(\.[0-9]+)?/;

View File

@@ -1,31 +1,5 @@
grammar Radar grammar Radar
// import "../common/common"; import "../common/common";
// Note: The import statement breaks TitleAndAccessibilities probably because of terminal order definition
// TODO: May need to change the common.langium to fix this
interface Common {
accDescr?: string;
accTitle?: string;
title?: string;
}
fragment TitleAndAccessibilities:
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) EOL)+
;
fragment EOL returns string:
NEWLINE+ | EOF
;
terminal NEWLINE: /\r?\n/;
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]*|)/;
hidden terminal WHITESPACE: /[\t ]+/;
hidden terminal YAML: /---[\t ]*\r?\n(?:[\S\s]*?\r?\n)?---(?:\r?\n|(?!\S))/;
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%(?:\r?\n|(?!\S))/;
hidden terminal SINGLE_LINE_COMMENT: /[\t ]*%%[^\n\r]*/;
entry Radar: entry Radar:
NEWLINE* NEWLINE*
@@ -78,12 +52,4 @@ Option:
) )
; ;
terminal NUMBER returns number: /(0|[1-9][0-9]*)(\.[0-9]+)?/;
terminal BOOLEAN returns boolean: 'true' | 'false';
terminal GRATICULE returns string: 'circle' | 'polygon'; terminal GRATICULE returns string: 'circle' | 'polygon';
terminal ID returns string: /[a-zA-Z_][a-zA-Z0-9\-_]*/;
terminal STRING: /"[^"]*"|'[^']*'/;

View File

@@ -0,0 +1,88 @@
import { describe, expect, it } from 'vitest';
import { Architecture } from '../src/language/index.js';
import { expectNoErrorsOrAlternatives, architectureParse as parse } from './test-util.js';
describe('architecture', () => {
describe('should handle architecture definition', () => {
it.each([
`architecture-beta`,
` architecture-beta `,
`\tarchitecture-beta\t`,
`
\tarchitecture-beta
`,
])('should handle regular architecture', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Architecture);
});
});
describe('should handle TitleAndAccessibilities', () => {
it.each([
`architecture-beta title sample title`,
` architecture-beta title sample title `,
`\tarchitecture-beta\ttitle sample title\t`,
`architecture-beta
\ttitle sample title
`,
])('should handle regular architecture + title in same line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Architecture);
const { title } = result.value;
expect(title).toBe('sample title');
});
it.each([
`architecture-beta
title sample title`,
`architecture-beta
title sample title
`,
])('should handle regular architecture + title in next line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Architecture);
const { title } = result.value;
expect(title).toBe('sample title');
});
it('should handle regular architecture + title + accTitle + accDescr', () => {
const context = `architecture-beta
title sample title
accTitle: sample accTitle
accDescr: sample accDescr
`;
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Architecture);
const { title, accTitle, accDescr } = result.value;
expect(title).toBe('sample title');
expect(accTitle).toBe('sample accTitle');
expect(accDescr).toBe('sample accDescr');
});
it('should handle regular architecture + title + accTitle + multi-line accDescr', () => {
const context = `architecture-beta
title sample title
accTitle: sample accTitle
accDescr {
sample accDescr
}
`;
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Architecture);
const { title, accTitle, accDescr } = result.value;
expect(title).toBe('sample title');
expect(accTitle).toBe('sample accTitle');
expect(accDescr).toBe('sample accDescr');
});
});
});

View File

@@ -63,6 +63,12 @@ describe('Parsing Branch Statements', () => {
expect(branch.name).toBe('master'); expect(branch.name).toBe('master');
}); });
it('should parse a branch name starting with numbers', () => {
const result = parse(`gitGraph\n commit\n branch 1.0.1\n`);
const branch = result.value.statements[1] as Branch;
expect(branch.name).toBe('1.0.1');
});
it('should parse a branch with an order property', () => { it('should parse a branch with an order property', () => {
const result = parse(`gitGraph\n commit\n branch feature order:1\n`); const result = parse(`gitGraph\n commit\n branch feature order:1\n`);
const branch = result.value.statements[1] as Branch; const branch = result.value.statements[1] as Branch;

View File

@@ -4,226 +4,247 @@ import { Pie } from '../src/language/index.js';
import { expectNoErrorsOrAlternatives, pieParse as parse } from './test-util.js'; import { expectNoErrorsOrAlternatives, pieParse as parse } from './test-util.js';
describe('pie', () => { describe('pie', () => {
it.each([ describe('should handle pie definition with or without showData', () => {
`pie`, it.each([
` pie `, `pie`,
`\tpie\t`, ` pie `,
` `\tpie\t`,
`
\tpie \tpie
`, `,
])('should handle regular pie', (context: string) => { ])('should handle regular pie', (context: string) => {
const result = parse(context); const result = parse(context);
expectNoErrorsOrAlternatives(result); expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie); expect(result.value.$type).toBe(Pie);
}); });
it.each([ it.each([
`pie showData`, `pie showData`,
` pie showData `, ` pie showData `,
`\tpie\tshowData\t`, `\tpie\tshowData\t`,
` `
pie\tshowData pie\tshowData
`, `,
])('should handle regular showData', (context: string) => { ])('should handle regular showData', (context: string) => {
const result = parse(context); const result = parse(context);
expectNoErrorsOrAlternatives(result); expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie); expect(result.value.$type).toBe(Pie);
const { showData } = result.value; const { showData } = result.value;
expect(showData).toBeTruthy(); expect(showData).toBeTruthy();
});
}); });
describe('should handle TitleAndAccessibilities', () => {
describe('should handle TitleAndAccessibilities without showData', () => {
it.each([
`pie title sample title`,
` pie title sample title `,
`\tpie\ttitle sample title\t`,
`pie
\ttitle sample title
`,
])('should handle regular pie + title in same line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
it.each([ const { title } = result.value;
`pie title sample title`, expect(title).toBe('sample title');
` pie title sample title `, });
`\tpie\ttitle sample title\t`,
`pie
\ttitle sample title
`,
])('should handle regular pie + title in same line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { title } = result.value;
expect(title).toBe('sample title');
});
it.each([
`pie
title sample title`,
`pie
title sample title
`,
`pie
title sample title`,
`pie
title sample title
`,
])('should handle regular pie + title in different line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { title } = result.value;
expect(title).toBe('sample title');
});
it.each([
`pie showData title sample title`,
`pie showData title sample title
`,
])('should handle regular pie + showData + title', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { showData, title } = result.value;
expect(showData).toBeTruthy();
expect(title).toBe('sample title');
});
it.each([
`pie showData
title sample title`,
`pie showData
title sample title
`,
`pie showData
title sample title`,
`pie showData
title sample title
`,
])('should handle regular showData + title in different line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { showData, title } = result.value;
expect(showData).toBeTruthy();
expect(title).toBe('sample title');
});
describe('sections', () => {
describe('normal', () => {
it.each([ it.each([
`pie `pie
title sample title`,
`pie
title sample title
`,
`pie
title sample title`,
`pie
title sample title
`,
])('should handle regular pie + title in different line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { title } = result.value;
expect(title).toBe('sample title');
});
});
describe('should handle TitleAndAccessibilities with showData', () => {
it.each([
`pie showData title sample title`,
`pie showData title sample title
`,
])('should handle regular pie + showData + title', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { showData, title } = result.value;
expect(showData).toBeTruthy();
expect(title).toBe('sample title');
});
it.each([
`pie showData
title sample title`,
`pie showData
title sample title
`,
`pie showData
title sample title`,
`pie showData
title sample title
`,
])('should handle regular showData + title in different line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { showData, title } = result.value;
expect(showData).toBeTruthy();
expect(title).toBe('sample title');
});
});
});
describe('should handle sections', () => {
it.each([
`pie
"GitHub":100 "GitHub":100
"GitLab":50`, "GitLab":50`,
`pie `pie
"GitHub" : 100 "GitHub" : 100
"GitLab" : 50`, "GitLab" : 50`,
`pie `pie
"GitHub"\t:\t100 "GitHub"\t:\t100
"GitLab"\t:\t50`, "GitLab"\t:\t50`,
`pie `pie
\t"GitHub" \t : \t 100 \t"GitHub" \t : \t 100
\t"GitLab" \t : \t 50 \t"GitLab" \t : \t 50
`, `,
])('should handle regular sections', (context: string) => { ])('should handle regular sections', (context: string) => {
const result = parse(context); const result = parse(context);
expectNoErrorsOrAlternatives(result); expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie); expect(result.value.$type).toBe(Pie);
const { sections } = result.value; const { sections } = result.value;
expect(sections[0].label).toBe('GitHub'); expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100); expect(sections[0].value).toBe(100);
expect(sections[1].label).toBe('GitLab'); expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50); expect(sections[1].value).toBe(50);
}); });
it('should handle sections with showData', () => { it('should handle sections with showData', () => {
const context = `pie showData const context = `pie showData
"GitHub": 100 "GitHub": 100
"GitLab": 50`; "GitLab": 50`;
const result = parse(context); const result = parse(context);
expectNoErrorsOrAlternatives(result); expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie); expect(result.value.$type).toBe(Pie);
const { showData, sections } = result.value; const { showData, sections } = result.value;
expect(showData).toBeTruthy(); expect(showData).toBeTruthy();
expect(sections[0].label).toBe('GitHub'); expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100); expect(sections[0].value).toBe(100);
expect(sections[1].label).toBe('GitLab'); expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50); expect(sections[1].value).toBe(50);
}); });
it('should handle sections with title', () => { it('should handle sections with title', () => {
const context = `pie title sample wow const context = `pie title sample wow
"GitHub": 100 "GitHub": 100
"GitLab": 50`; "GitLab": 50`;
const result = parse(context); const result = parse(context);
expectNoErrorsOrAlternatives(result); expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie); expect(result.value.$type).toBe(Pie);
const { title, sections } = result.value; const { title, sections } = result.value;
expect(title).toBe('sample wow'); expect(title).toBe('sample wow');
expect(sections[0].label).toBe('GitHub'); expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100); expect(sections[0].value).toBe(100);
expect(sections[1].label).toBe('GitLab'); expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50); expect(sections[1].value).toBe(50);
}); });
it('should handle sections with accTitle', () => { it('should handle value with positive decimal', () => {
const context = `pie accTitle: sample wow const context = `pie
"ash": 60.67
"bat": 40`;
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { sections } = result.value;
expect(sections[0].label).toBe('ash');
expect(sections[0].value).toBe(60.67);
expect(sections[1].label).toBe('bat');
expect(sections[1].value).toBe(40);
});
it('should handle sections with accTitle', () => {
const context = `pie accTitle: sample wow
"GitHub": 100 "GitHub": 100
"GitLab": 50`; "GitLab": 50`;
const result = parse(context); const result = parse(context);
expectNoErrorsOrAlternatives(result); expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie); expect(result.value.$type).toBe(Pie);
const { accTitle, sections } = result.value; const { accTitle, sections } = result.value;
expect(accTitle).toBe('sample wow'); expect(accTitle).toBe('sample wow');
expect(sections[0].label).toBe('GitHub'); expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100); expect(sections[0].value).toBe(100);
expect(sections[1].label).toBe('GitLab'); expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50); expect(sections[1].value).toBe(50);
}); });
it('should handle sections with single line accDescr', () => { it('should handle sections with single line accDescr', () => {
const context = `pie accDescr: sample wow const context = `pie accDescr: sample wow
"GitHub": 100 "GitHub": 100
"GitLab": 50`; "GitLab": 50`;
const result = parse(context); const result = parse(context);
expectNoErrorsOrAlternatives(result); expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie); expect(result.value.$type).toBe(Pie);
const { accDescr, sections } = result.value; const { accDescr, sections } = result.value;
expect(accDescr).toBe('sample wow'); expect(accDescr).toBe('sample wow');
expect(sections[0].label).toBe('GitHub'); expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100); expect(sections[0].value).toBe(100);
expect(sections[1].label).toBe('GitLab'); expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50); expect(sections[1].value).toBe(50);
}); });
it('should handle sections with multi line accDescr', () => { it('should handle sections with multi line accDescr', () => {
const context = `pie accDescr { const context = `pie accDescr {
sample wow sample wow
} }
"GitHub": 100 "GitHub": 100
"GitLab": 50`; "GitLab": 50`;
const result = parse(context); const result = parse(context);
expectNoErrorsOrAlternatives(result); expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie); expect(result.value.$type).toBe(Pie);
const { accDescr, sections } = result.value; const { accDescr, sections } = result.value;
expect(accDescr).toBe('sample wow'); expect(accDescr).toBe('sample wow');
expect(sections[0].label).toBe('GitHub'); expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100); expect(sections[0].value).toBe(100);
expect(sections[1].label).toBe('GitLab'); expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50); expect(sections[1].value).toBe(50);
});
}); });
}); });
}); });

View File

@@ -1,6 +1,8 @@
import type { LangiumParser, ParseResult } from 'langium'; import type { LangiumParser, ParseResult } from 'langium';
import { expect, vi } from 'vitest'; import { expect, vi } from 'vitest';
import type { import type {
Architecture,
ArchitectureServices,
Info, Info,
InfoServices, InfoServices,
Pie, Pie,
@@ -13,6 +15,7 @@ import type {
GitGraphServices, GitGraphServices,
} from '../src/language/index.js'; } from '../src/language/index.js';
import { import {
createArchitectureServices,
createInfoServices, createInfoServices,
createPieServices, createPieServices,
createRadarServices, createRadarServices,
@@ -47,6 +50,17 @@ export function createInfoTestServices() {
} }
export const infoParse = createInfoTestServices().parse; export const infoParse = createInfoTestServices().parse;
const architectureServices: ArchitectureServices = createArchitectureServices().Architecture;
const architectureParser: LangiumParser = architectureServices.parser.LangiumParser;
export function createArchitectureTestServices() {
const parse = (input: string) => {
return architectureParser.parse<Architecture>(input);
};
return { services: architectureServices, parse };
}
export const architectureParse = createArchitectureTestServices().parse;
const pieServices: PieServices = createPieServices().Pie; const pieServices: PieServices = createPieServices().Pie;
const pieParser: LangiumParser = pieServices.parser.LangiumParser; const pieParser: LangiumParser = pieServices.parser.LangiumParser;
export function createPieTestServices() { export function createPieTestServices() {