mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-11-03 20:34:20 +01:00 
			
		
		
		
	🖋️ Add grammar for Radar chart
This commit is contained in:
		@@ -27,6 +27,7 @@ const MERMAID_CONFIG_DIAGRAM_KEYS = [
 | 
			
		||||
  'block',
 | 
			
		||||
  'packet',
 | 
			
		||||
  'architecture',
 | 
			
		||||
  'radar',
 | 
			
		||||
] as const;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,11 @@
 | 
			
		||||
      "id": "gitGraph",
 | 
			
		||||
      "grammar": "src/language/gitGraph/gitGraph.langium",
 | 
			
		||||
      "fileExtensions": [".mmd", ".mermaid"]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "radar",
 | 
			
		||||
      "grammar": "src/language/radar/radar.langium",
 | 
			
		||||
      "fileExtensions": [".mmd", ".mermaid"]
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "mode": "production",
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ export {
 | 
			
		||||
  PieSection,
 | 
			
		||||
  Architecture,
 | 
			
		||||
  GitGraph,
 | 
			
		||||
  Radar,
 | 
			
		||||
  Branch,
 | 
			
		||||
  Commit,
 | 
			
		||||
  Merge,
 | 
			
		||||
@@ -31,6 +32,7 @@ export {
 | 
			
		||||
  PieGeneratedModule,
 | 
			
		||||
  ArchitectureGeneratedModule,
 | 
			
		||||
  GitGraphGeneratedModule,
 | 
			
		||||
  RadarGeneratedModule,
 | 
			
		||||
} from './generated/module.js';
 | 
			
		||||
 | 
			
		||||
export * from './gitGraph/index.js';
 | 
			
		||||
@@ -39,3 +41,4 @@ export * from './info/index.js';
 | 
			
		||||
export * from './packet/index.js';
 | 
			
		||||
export * from './pie/index.js';
 | 
			
		||||
export * from './architecture/index.js';
 | 
			
		||||
export * from './radar/index.js';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								packages/parser/src/language/radar/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/parser/src/language/radar/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
export * from './module.js';
 | 
			
		||||
							
								
								
									
										73
									
								
								packages/parser/src/language/radar/module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								packages/parser/src/language/radar/module.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
import type {
 | 
			
		||||
  DefaultSharedCoreModuleContext,
 | 
			
		||||
  LangiumCoreServices,
 | 
			
		||||
  LangiumSharedCoreServices,
 | 
			
		||||
  Module,
 | 
			
		||||
  PartialLangiumCoreServices,
 | 
			
		||||
} from 'langium';
 | 
			
		||||
import {
 | 
			
		||||
  EmptyFileSystem,
 | 
			
		||||
  createDefaultCoreModule,
 | 
			
		||||
  createDefaultSharedCoreModule,
 | 
			
		||||
  inject,
 | 
			
		||||
} from 'langium';
 | 
			
		||||
import { CommonValueConverter } from '../common/valueConverter.js';
 | 
			
		||||
import { MermaidGeneratedSharedModule, RadarGeneratedModule } from '../generated/module.js';
 | 
			
		||||
import { RadarTokenBuilder } from './tokenBuilder.js';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Declaration of `Radar` services.
 | 
			
		||||
 */
 | 
			
		||||
interface RadarAddedServices {
 | 
			
		||||
  parser: {
 | 
			
		||||
    TokenBuilder: RadarTokenBuilder;
 | 
			
		||||
    ValueConverter: CommonValueConverter;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Union of Langium default services and `Radar` services.
 | 
			
		||||
 */
 | 
			
		||||
export type RadarServices = LangiumCoreServices & RadarAddedServices;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Dependency injection module that overrides Langium default services and
 | 
			
		||||
 * contributes the declared `Radar` services.
 | 
			
		||||
 */
 | 
			
		||||
export const RadarModule: Module<RadarServices, PartialLangiumCoreServices & RadarAddedServices> = {
 | 
			
		||||
  parser: {
 | 
			
		||||
    TokenBuilder: () => new RadarTokenBuilder(),
 | 
			
		||||
    ValueConverter: () => new CommonValueConverter(),
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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 createRadarServices(context: DefaultSharedCoreModuleContext = EmptyFileSystem): {
 | 
			
		||||
  shared: LangiumSharedCoreServices;
 | 
			
		||||
  Radar: RadarServices;
 | 
			
		||||
} {
 | 
			
		||||
  const shared: LangiumSharedCoreServices = inject(
 | 
			
		||||
    createDefaultSharedCoreModule(context),
 | 
			
		||||
    MermaidGeneratedSharedModule
 | 
			
		||||
  );
 | 
			
		||||
  const Radar: RadarServices = inject(
 | 
			
		||||
    createDefaultCoreModule({ shared }),
 | 
			
		||||
    RadarGeneratedModule,
 | 
			
		||||
    RadarModule
 | 
			
		||||
  );
 | 
			
		||||
  shared.ServiceRegistry.register(Radar);
 | 
			
		||||
  return { shared, Radar };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										89
									
								
								packages/parser/src/language/radar/radar.langium
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								packages/parser/src/language/radar/radar.langium
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
grammar Radar
 | 
			
		||||
// 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:
 | 
			
		||||
    NEWLINE*
 | 
			
		||||
    ('radar-beta' | 'radar-beta:' | 'radar-beta' ':')
 | 
			
		||||
    NEWLINE*
 | 
			
		||||
    (
 | 
			
		||||
        TitleAndAccessibilities
 | 
			
		||||
        | 'axis' axes+=Axis (',' axes+=Axis)*
 | 
			
		||||
        | 'curve' curves+=Curve (',' curves+=Curve)*
 | 
			
		||||
        | options+=Option (',' options+=Option)*
 | 
			
		||||
        | NEWLINE
 | 
			
		||||
    )*
 | 
			
		||||
;
 | 
			
		||||
 | 
			
		||||
fragment Label:
 | 
			
		||||
    '[' label=STRING ']'
 | 
			
		||||
;
 | 
			
		||||
 | 
			
		||||
Axis:
 | 
			
		||||
    name=ID (Label)? 
 | 
			
		||||
;
 | 
			
		||||
 | 
			
		||||
Curve:
 | 
			
		||||
    name=ID (Label)? '{' Entries '}'
 | 
			
		||||
;
 | 
			
		||||
 | 
			
		||||
fragment Entries:
 | 
			
		||||
    NEWLINE* entries+=NumberEntry (',' NEWLINE* entries+=NumberEntry)* NEWLINE* |
 | 
			
		||||
    NEWLINE* entries+=DetailedEntry (',' NEWLINE* entries+=DetailedEntry)* NEWLINE*
 | 
			
		||||
;
 | 
			
		||||
 | 
			
		||||
interface Entry {
 | 
			
		||||
    axis?: @Axis;
 | 
			
		||||
    value: number;
 | 
			
		||||
}
 | 
			
		||||
DetailedEntry returns Entry:
 | 
			
		||||
    axis=[Axis:ID] ':'? value=NUMBER
 | 
			
		||||
;
 | 
			
		||||
NumberEntry returns Entry:
 | 
			
		||||
    value=NUMBER
 | 
			
		||||
;
 | 
			
		||||
 | 
			
		||||
Option:
 | 
			
		||||
    (
 | 
			
		||||
        name='showLegend' value=BOOLEAN
 | 
			
		||||
        | name='ticks' value=NUMBER
 | 
			
		||||
        | name='max' value=NUMBER
 | 
			
		||||
        | name='min' value=NUMBER
 | 
			
		||||
        | name='graticule' value=GRATICULE
 | 
			
		||||
    )
 | 
			
		||||
;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
terminal NUMBER returns number: /(0|[1-9][0-9]*)(\.[0-9]+)?/;
 | 
			
		||||
 | 
			
		||||
terminal BOOLEAN returns boolean: 'true' | 'false';    
 | 
			
		||||
 | 
			
		||||
terminal GRATICULE returns string: 'circle' | 'polygon';
 | 
			
		||||
 | 
			
		||||
terminal ID returns string: /[a-zA-Z_][a-zA-Z0-9\-_]*/; 
 | 
			
		||||
terminal STRING: /"[^"]*"|'[^']*'/;
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/parser/src/language/radar/tokenBuilder.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/parser/src/language/radar/tokenBuilder.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import { AbstractMermaidTokenBuilder } from '../common/index.js';
 | 
			
		||||
 | 
			
		||||
export class RadarTokenBuilder extends AbstractMermaidTokenBuilder {
 | 
			
		||||
  public constructor() {
 | 
			
		||||
    super(['radar-beta']);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import type { LangiumParser, ParseResult } from 'langium';
 | 
			
		||||
 | 
			
		||||
import type { Info, Packet, Pie, Architecture, GitGraph } from './index.js';
 | 
			
		||||
import type { Info, Packet, Pie, Architecture, GitGraph, Radar } from './index.js';
 | 
			
		||||
 | 
			
		||||
export type DiagramAST = Info | Packet | Pie | Architecture | GitGraph;
 | 
			
		||||
export type DiagramAST = Info | Packet | Pie | Architecture | GitGraph | Radar;
 | 
			
		||||
 | 
			
		||||
const parsers: Record<string, LangiumParser> = {};
 | 
			
		||||
const initializers = {
 | 
			
		||||
@@ -31,6 +31,11 @@ const initializers = {
 | 
			
		||||
    const parser = createGitGraphServices().GitGraph.parser.LangiumParser;
 | 
			
		||||
    parsers.gitGraph = parser;
 | 
			
		||||
  },
 | 
			
		||||
  radar: async () => {
 | 
			
		||||
    const { createRadarServices } = await import('./language/radar/index.js');
 | 
			
		||||
    const parser = createRadarServices().Radar.parser.LangiumParser;
 | 
			
		||||
    parsers.radar = parser;
 | 
			
		||||
  },
 | 
			
		||||
} as const;
 | 
			
		||||
 | 
			
		||||
export async function parse(diagramType: 'info', text: string): Promise<Info>;
 | 
			
		||||
@@ -38,6 +43,7 @@ export async function parse(diagramType: 'packet', text: string): Promise<Packet
 | 
			
		||||
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<T extends DiagramAST>(
 | 
			
		||||
  diagramType: keyof typeof initializers,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								packages/parser/tests/packet.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								packages/parser/tests/packet.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import { describe, expect, it } from 'vitest';
 | 
			
		||||
 | 
			
		||||
import { Packet } from '../src/language/index.js';
 | 
			
		||||
import { expectNoErrorsOrAlternatives, packetParse as parse } from './test-util.js';
 | 
			
		||||
 | 
			
		||||
describe('packet', () => {
 | 
			
		||||
  it.each([
 | 
			
		||||
    `packet-beta`,
 | 
			
		||||
    `  packet-beta  `,
 | 
			
		||||
    `\tpacket-beta\t`,
 | 
			
		||||
    `
 | 
			
		||||
    \tpacket-beta
 | 
			
		||||
    `,
 | 
			
		||||
  ])('should handle regular packet', (context: string) => {
 | 
			
		||||
    const result = parse(context);
 | 
			
		||||
    expectNoErrorsOrAlternatives(result);
 | 
			
		||||
    expect(result.value.$type).toBe(Packet);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										343
									
								
								packages/parser/tests/radar.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										343
									
								
								packages/parser/tests/radar.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,343 @@
 | 
			
		||||
import { describe, expect, it } from 'vitest';
 | 
			
		||||
 | 
			
		||||
import { Radar } from '../src/language/index.js';
 | 
			
		||||
import { expectNoErrorsOrAlternatives, radarParse as parse } from './test-util.js';
 | 
			
		||||
 | 
			
		||||
const mutateGlobalSpacing = (context: string) => {
 | 
			
		||||
  return [
 | 
			
		||||
    context,
 | 
			
		||||
    `  ${context}  `,
 | 
			
		||||
    `\t${context}\t`,
 | 
			
		||||
    `
 | 
			
		||||
    \t${context}
 | 
			
		||||
    `,
 | 
			
		||||
  ];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('radar', () => {
 | 
			
		||||
  it.each([
 | 
			
		||||
    ...mutateGlobalSpacing('radar-beta'),
 | 
			
		||||
    ...mutateGlobalSpacing('radar-beta:'),
 | 
			
		||||
    ...mutateGlobalSpacing('radar-beta :'),
 | 
			
		||||
  ])('should handle regular radar', (context: string) => {
 | 
			
		||||
    const result = parse(context);
 | 
			
		||||
    expectNoErrorsOrAlternatives(result);
 | 
			
		||||
    expect(result.value.$type).toBe(Radar);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('should handle title, accDescr, and accTitle', () => {
 | 
			
		||||
    it.each([
 | 
			
		||||
      ...mutateGlobalSpacing(' title My Title'),
 | 
			
		||||
      ...mutateGlobalSpacing('\n  title My Title'),
 | 
			
		||||
    ])('should handle title', (context: string) => {
 | 
			
		||||
      const result = parse(`radar-beta${context}`);
 | 
			
		||||
      expectNoErrorsOrAlternatives(result);
 | 
			
		||||
      expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
      const { title } = result.value;
 | 
			
		||||
      expect(title).toBe('My Title');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it.each([
 | 
			
		||||
      ...mutateGlobalSpacing(' accDescr: My Accessible Description'),
 | 
			
		||||
      ...mutateGlobalSpacing('\n  accDescr: My Accessible Description'),
 | 
			
		||||
    ])('should handle accDescr', (context: string) => {
 | 
			
		||||
      const result = parse(`radar-beta${context}`);
 | 
			
		||||
      expectNoErrorsOrAlternatives(result);
 | 
			
		||||
      expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
      const { accDescr } = result.value;
 | 
			
		||||
      expect(accDescr).toBe('My Accessible Description');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it.each([
 | 
			
		||||
      ...mutateGlobalSpacing(' accTitle: My Accessible Title'),
 | 
			
		||||
      ...mutateGlobalSpacing('\n  accTitle: My Accessible Title'),
 | 
			
		||||
    ])('should handle accTitle', (context: string) => {
 | 
			
		||||
      const result = parse(`radar-beta${context}`);
 | 
			
		||||
      expectNoErrorsOrAlternatives(result);
 | 
			
		||||
      expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
      const { accTitle } = result.value;
 | 
			
		||||
      expect(accTitle).toBe('My Accessible Title');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it.each([
 | 
			
		||||
      ...mutateGlobalSpacing(
 | 
			
		||||
        ' title My Title\n  accDescr: My Accessible Description\n  accTitle: My Accessible Title'
 | 
			
		||||
      ),
 | 
			
		||||
      ...mutateGlobalSpacing(
 | 
			
		||||
        '\n  title My Title\n  accDescr: My Accessible Description\n  accTitle: My Accessible Title'
 | 
			
		||||
      ),
 | 
			
		||||
    ])('should handle title + accDescr + accTitle', (context: string) => {
 | 
			
		||||
      const result = parse(`radar-beta${context}`);
 | 
			
		||||
      expectNoErrorsOrAlternatives(result);
 | 
			
		||||
      expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
      const { title, accDescr, accTitle } = result.value;
 | 
			
		||||
      expect(title).toBe('My Title');
 | 
			
		||||
      expect(accDescr).toBe('My Accessible Description');
 | 
			
		||||
      expect(accTitle).toBe('My Accessible Title');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('should handle axis', () => {
 | 
			
		||||
    it.each([`axis my-axis`, `axis my-axis["My Axis Label"]`])(
 | 
			
		||||
      'should handle one axis',
 | 
			
		||||
      (context: string) => {
 | 
			
		||||
        const result = parse(`radar-beta\n${context}`);
 | 
			
		||||
        expectNoErrorsOrAlternatives(result);
 | 
			
		||||
        expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
        const { axes } = result.value;
 | 
			
		||||
        expect(axes).toHaveLength(1);
 | 
			
		||||
        expect(axes[0].$type).toBe('Axis');
 | 
			
		||||
        expect(axes[0].name).toBe('my-axis');
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    it.each([
 | 
			
		||||
      `axis my-axis["My Axis Label"]
 | 
			
		||||
      axis my-axis2`,
 | 
			
		||||
      `axis my-axis, my-axis2`,
 | 
			
		||||
      `axis my-axis["My Axis Label"], my-axis2`,
 | 
			
		||||
      `axis my-axis, my-axis2["My Second Axis Label"]`,
 | 
			
		||||
    ])('should handle multiple axes', (context: string) => {
 | 
			
		||||
      const result = parse(`radar-beta\n${context}`);
 | 
			
		||||
      expectNoErrorsOrAlternatives(result);
 | 
			
		||||
      expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
      const { axes } = result.value;
 | 
			
		||||
      expect(axes).toHaveLength(2);
 | 
			
		||||
      expect(axes.every((axis) => axis.$type === 'Axis')).toBe(true);
 | 
			
		||||
      expect(axes[0].name).toBe('my-axis');
 | 
			
		||||
      expect(axes[1].name).toBe('my-axis2');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it.each([
 | 
			
		||||
      `axis my-axis["My Axis Label"]
 | 
			
		||||
      axis my-axis2["My Second Axis Label"]`,
 | 
			
		||||
      `axis my-axis ["My Axis Label"], my-axis2\t["My Second Axis Label"]`,
 | 
			
		||||
    ])('should handle axis labels', (context: string) => {
 | 
			
		||||
      const result = parse(`radar-beta\n${context}`);
 | 
			
		||||
      expectNoErrorsOrAlternatives(result);
 | 
			
		||||
      expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
      const { axes } = result.value;
 | 
			
		||||
      expect(axes).toHaveLength(2);
 | 
			
		||||
      expect(axes[0].name).toBe('my-axis');
 | 
			
		||||
      expect(axes[0].label).toBe('My Axis Label');
 | 
			
		||||
      expect(axes[1].name).toBe('my-axis2');
 | 
			
		||||
      expect(axes[1].label).toBe('My Second Axis Label');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not allow empty axis names', () => {
 | 
			
		||||
      const result = parse(`radar-beta
 | 
			
		||||
      axis`);
 | 
			
		||||
      expect(result.parserErrors).not.toHaveLength(0);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not allow non-comma separated axis names', () => {
 | 
			
		||||
      const result = parse(`radar-beta
 | 
			
		||||
      axis my-axis my-axis2`);
 | 
			
		||||
      expect(result.parserErrors).not.toHaveLength(0);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('should handle curves', () => {
 | 
			
		||||
    it.each([
 | 
			
		||||
      `radar-beta
 | 
			
		||||
      curve my-curve`,
 | 
			
		||||
      `radar-beta
 | 
			
		||||
      curve my-curve["My Curve Label"]`,
 | 
			
		||||
    ])('should not allow curves without axes', (context: string) => {
 | 
			
		||||
      const result = parse(`radar-beta${context}`);
 | 
			
		||||
      expect(result.parserErrors).not.toHaveLength(0);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it.each([
 | 
			
		||||
      `radar-beta
 | 
			
		||||
      axis my-axis
 | 
			
		||||
      curve my-curve`,
 | 
			
		||||
      `radar-beta
 | 
			
		||||
      axis my-axis
 | 
			
		||||
      curve my-curve["My Curve Label"]`,
 | 
			
		||||
    ])('should not allow curves without entries', (context: string) => {
 | 
			
		||||
      const result = parse(`radar-beta${context}`);
 | 
			
		||||
      expect(result.parserErrors).not.toHaveLength(0);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it.each([
 | 
			
		||||
      `curve my-curve { 1 }`,
 | 
			
		||||
      `curve my-curve {
 | 
			
		||||
        1
 | 
			
		||||
      }`,
 | 
			
		||||
      `curve my-curve {
 | 
			
		||||
 | 
			
		||||
      1
 | 
			
		||||
 | 
			
		||||
      }`,
 | 
			
		||||
    ])('should handle one curve with one entry', (context: string) => {
 | 
			
		||||
      const result = parse(`radar-beta\naxis my-axis\n${context}`);
 | 
			
		||||
      expectNoErrorsOrAlternatives(result);
 | 
			
		||||
      expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
      const { curves } = result.value;
 | 
			
		||||
      expect(curves).toHaveLength(1);
 | 
			
		||||
      expect(curves[0].$type).toBe('Curve');
 | 
			
		||||
      expect(curves[0].name).toBe('my-curve');
 | 
			
		||||
      expect(curves[0].entries).toHaveLength(1);
 | 
			
		||||
      expect(curves[0].entries[0].$type).toBe('Entry');
 | 
			
		||||
      expect(curves[0].entries[0].value).toBe(1);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it.each([
 | 
			
		||||
      `curve my-curve { my-axis 1 }`,
 | 
			
		||||
      `curve my-curve { my-axis : 1 }`,
 | 
			
		||||
      `curve my-curve {
 | 
			
		||||
        my-axis: 1
 | 
			
		||||
      }`,
 | 
			
		||||
    ])('should handle one curve with one detailed entry', (context: string) => {
 | 
			
		||||
      const result = parse(`radar-beta\naxis my-axis\n${context}`);
 | 
			
		||||
      expectNoErrorsOrAlternatives(result);
 | 
			
		||||
      expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
      const { curves } = result.value;
 | 
			
		||||
      expect(curves).toHaveLength(1);
 | 
			
		||||
      expect(curves[0].$type).toBe('Curve');
 | 
			
		||||
      expect(curves[0].name).toBe('my-curve');
 | 
			
		||||
      expect(curves[0].entries).toHaveLength(1);
 | 
			
		||||
      expect(curves[0].entries[0].$type).toBe('Entry');
 | 
			
		||||
      expect(curves[0].entries[0].value).toBe(1);
 | 
			
		||||
      expect(curves[0].entries[0]?.axis?.$refText).toBe('my-axis');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it.each([
 | 
			
		||||
      `curve my-curve { ax1 1, ax2 2 }`,
 | 
			
		||||
      `curve my-curve {
 | 
			
		||||
        ax1 1,
 | 
			
		||||
        ax2 2
 | 
			
		||||
      }`,
 | 
			
		||||
      `curve my-curve["My Curve Label"] {
 | 
			
		||||
        ax1: 1, ax2: 2
 | 
			
		||||
      }`,
 | 
			
		||||
    ])('should handle one curve with multiple detailed entries', (context: string) => {
 | 
			
		||||
      const result = parse(`radar-beta\naxis ax1, ax1\n${context}`);
 | 
			
		||||
      expectNoErrorsOrAlternatives(result);
 | 
			
		||||
      expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
      const { curves } = result.value;
 | 
			
		||||
      expect(curves).toHaveLength(1);
 | 
			
		||||
      expect(curves[0].$type).toBe('Curve');
 | 
			
		||||
      expect(curves[0].name).toBe('my-curve');
 | 
			
		||||
      expect(curves[0].entries).toHaveLength(2);
 | 
			
		||||
      expect(curves[0].entries[0].$type).toBe('Entry');
 | 
			
		||||
      expect(curves[0].entries[0].value).toBe(1);
 | 
			
		||||
      expect(curves[0].entries[0]?.axis?.$refText).toBe('ax1');
 | 
			
		||||
      expect(curves[0].entries[1].$type).toBe('Entry');
 | 
			
		||||
      expect(curves[0].entries[1].value).toBe(2);
 | 
			
		||||
      expect(curves[0].entries[1]?.axis?.$refText).toBe('ax2');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it.each([
 | 
			
		||||
      `curve c1 { ax1 1, ax2 2 }
 | 
			
		||||
      curve c2 { ax1 3, ax2 4 }`,
 | 
			
		||||
      `curve c1 {
 | 
			
		||||
        ax1 1,
 | 
			
		||||
        ax2 2
 | 
			
		||||
      }
 | 
			
		||||
      curve c2 {
 | 
			
		||||
        ax1 3,
 | 
			
		||||
        ax2 4
 | 
			
		||||
      }`,
 | 
			
		||||
      `curve c1{ 1, 2 }, c2{ 3, 4 }`,
 | 
			
		||||
    ])('should handle multiple curves', (context: string) => {
 | 
			
		||||
      const result = parse(`radar-beta\naxis ax1, ax1\n${context}`);
 | 
			
		||||
      expectNoErrorsOrAlternatives(result);
 | 
			
		||||
      expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
      const { curves } = result.value;
 | 
			
		||||
      expect(curves).toHaveLength(2);
 | 
			
		||||
      expect(curves.every((curve) => curve.$type === 'Curve')).toBe(true);
 | 
			
		||||
      expect(curves[0].name).toBe('c1');
 | 
			
		||||
      expect(curves[1].name).toBe('c2');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not allow empty curve names', () => {
 | 
			
		||||
      const result = parse(`radar-beta
 | 
			
		||||
      axis my-axis
 | 
			
		||||
      curve`);
 | 
			
		||||
      expect(result.parserErrors).not.toHaveLength(0);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not allow number and detailed entries in the same curve', () => {
 | 
			
		||||
      const result = parse(`radar-beta
 | 
			
		||||
      axis ax1, ax2
 | 
			
		||||
      curve my-curve { 1, ax1 2 }`);
 | 
			
		||||
      expect(result.parserErrors).not.toHaveLength(0);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not allow non-comma separated entries', () => {
 | 
			
		||||
      const result = parse(`radar-beta
 | 
			
		||||
      axis ax1, ax2
 | 
			
		||||
      curve my-curve { ax1 1 ax2 2 }`);
 | 
			
		||||
      expect(result.parserErrors).not.toHaveLength(0);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('should handle options', () => {
 | 
			
		||||
    it.each([`ticks 5`, `min 50`, `max 50`])(
 | 
			
		||||
      `should handle number option %s`,
 | 
			
		||||
      (context: string) => {
 | 
			
		||||
        const result = parse(`radar-beta
 | 
			
		||||
      axis ax1, ax2
 | 
			
		||||
      curve c1 { ax1 1, ax2 2 }
 | 
			
		||||
      ${context}`);
 | 
			
		||||
        expectNoErrorsOrAlternatives(result);
 | 
			
		||||
        expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
        const { options } = result.value;
 | 
			
		||||
        expect(options).toBeDefined();
 | 
			
		||||
        const option = options.find((option) => option.name === context.split(' ')[0]);
 | 
			
		||||
        expect(option).toBeDefined();
 | 
			
		||||
        expect(option?.value).toBe(Number(context.split(' ')[1]));
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    it.each([`graticule circle`, `graticule polygon`])(
 | 
			
		||||
      `should handle string option %s`,
 | 
			
		||||
      (context: string) => {
 | 
			
		||||
        const result = parse(`radar-beta
 | 
			
		||||
      axis ax1, ax2
 | 
			
		||||
      curve c1 { ax1 1, ax2 2 }
 | 
			
		||||
      ${context}`);
 | 
			
		||||
        expectNoErrorsOrAlternatives(result);
 | 
			
		||||
        expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
        const { options } = result.value;
 | 
			
		||||
        expect(options).toBeDefined();
 | 
			
		||||
        const option = options.find((option) => option.name === context.split(' ')[0]);
 | 
			
		||||
        expect(option).toBeDefined();
 | 
			
		||||
        expect(option?.value).toBe(context.split(' ')[1]);
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    it.each([`showLegend true`, `showLegend false`])(
 | 
			
		||||
      `should handle boolean option %s`,
 | 
			
		||||
      (context: string) => {
 | 
			
		||||
        const result = parse(`radar-beta
 | 
			
		||||
      axis ax1, ax2
 | 
			
		||||
      curve c1 { ax1 1, ax2 2 }
 | 
			
		||||
      ${context}`);
 | 
			
		||||
        expectNoErrorsOrAlternatives(result);
 | 
			
		||||
        expect(result.value.$type).toBe(Radar);
 | 
			
		||||
 | 
			
		||||
        const { options } = result.value;
 | 
			
		||||
        expect(options).toBeDefined();
 | 
			
		||||
        const option = options.find((option) => option.name === context.split(' ')[0]);
 | 
			
		||||
        expect(option).toBeDefined();
 | 
			
		||||
        expect(option?.value).toBe(context.split(' ')[1] === 'true');
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -5,12 +5,18 @@ import type {
 | 
			
		||||
  InfoServices,
 | 
			
		||||
  Pie,
 | 
			
		||||
  PieServices,
 | 
			
		||||
  Radar,
 | 
			
		||||
  RadarServices,
 | 
			
		||||
  Packet,
 | 
			
		||||
  PacketServices,
 | 
			
		||||
  GitGraph,
 | 
			
		||||
  GitGraphServices,
 | 
			
		||||
} from '../src/language/index.js';
 | 
			
		||||
import {
 | 
			
		||||
  createInfoServices,
 | 
			
		||||
  createPieServices,
 | 
			
		||||
  createRadarServices,
 | 
			
		||||
  createPacketServices,
 | 
			
		||||
  createGitGraphServices,
 | 
			
		||||
} from '../src/language/index.js';
 | 
			
		||||
 | 
			
		||||
@@ -52,6 +58,28 @@ export function createPieTestServices() {
 | 
			
		||||
}
 | 
			
		||||
export const pieParse = createPieTestServices().parse;
 | 
			
		||||
 | 
			
		||||
const packetServices: PacketServices = createPacketServices().Packet;
 | 
			
		||||
const packetParser: LangiumParser = packetServices.parser.LangiumParser;
 | 
			
		||||
export function createPacketTestServices() {
 | 
			
		||||
  const parse = (input: string) => {
 | 
			
		||||
    return packetParser.parse<Packet>(input);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return { services: packetServices, parse };
 | 
			
		||||
}
 | 
			
		||||
export const packetParse = createPacketTestServices().parse;
 | 
			
		||||
 | 
			
		||||
const radarServices: RadarServices = createRadarServices().Radar;
 | 
			
		||||
const radarParser: LangiumParser = radarServices.parser.LangiumParser;
 | 
			
		||||
export function createRadarTestServices() {
 | 
			
		||||
  const parse = (input: string) => {
 | 
			
		||||
    return radarParser.parse<Radar>(input);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return { services: radarServices, parse };
 | 
			
		||||
}
 | 
			
		||||
export const radarParse = createRadarTestServices().parse;
 | 
			
		||||
 | 
			
		||||
const gitGraphServices: GitGraphServices = createGitGraphServices().GitGraph;
 | 
			
		||||
const gitGraphParser: LangiumParser = gitGraphServices.parser.LangiumParser;
 | 
			
		||||
export function createGitGraphTestServices() {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user