mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-19 16:26:43 +02:00
Merge branch 'next' into sidv/tinyMermaid
* next: (27 commits) chore: Fix type refactor!: remove MermaidConfig type enum fallback test: rewrite some `config` vals to tighten types chore: Add comment for `yy`. chore: Increase heap size when building chore: increase `test-util.ts` converage by returning `undefined` chore: add `vitest` imports to `test-util.ts` chore: run `pnpm lint:fix` create `noErrorsOrAlternatives` parser helper function chore: export `InfoModule` from `infoModule.ts` docs(parser): create `packages/parser` README.md file build: build `.langium` file using `generate` from `langium-cli` build: update `langium` and `langium-cli` to `v2.0.1` fix: fix if statment logic checks if `parser` is not `undefined` chore: add a comment illustrate why we build packages sequentially chore: refactore `&&` into `if` in `populateCommonDb` chore: remove `./*` part from `exports` in `parser/package.json` fix: use `execFileSync` instead of `execSync` in `generateLangium` fix(mermaid): mark `mermaid-parser` dependecy with `^` reorder `packages/parser` after `packages/mermaid/src/vitepress` ...
This commit is contained in:
@@ -2,6 +2,11 @@
|
||||
* Shared common options for both ESBuild and Vite
|
||||
*/
|
||||
export const packageOptions = {
|
||||
parser: {
|
||||
name: 'mermaid-parser',
|
||||
packageName: 'parser',
|
||||
file: 'index.ts',
|
||||
},
|
||||
mermaid: {
|
||||
name: 'mermaid',
|
||||
packageName: 'mermaid',
|
||||
|
5
.build/generateLangium.ts
Normal file
5
.build/generateLangium.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { generate } from 'langium-cli';
|
||||
|
||||
export async function generateLangium() {
|
||||
await generate({ file: `./packages/parser/langium-config.json` });
|
||||
}
|
9
.build/langium-cli.d.ts
vendored
Normal file
9
.build/langium-cli.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
declare module 'langium-cli' {
|
||||
export interface GenerateOptions {
|
||||
file?: string;
|
||||
mode?: 'development' | 'production';
|
||||
watch?: boolean;
|
||||
}
|
||||
|
||||
export function generate(options: GenerateOptions): Promise<boolean>;
|
||||
}
|
@@ -2,6 +2,7 @@ import { build } from 'esbuild';
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import { MermaidBuildOptions, defaultOptions, getBuildConfig } from './util.js';
|
||||
import { packageOptions } from '../.build/common.js';
|
||||
import { generateLangium } from '../.build/generateLangium.js';
|
||||
|
||||
const shouldVisualize = process.argv.includes('--visualize');
|
||||
|
||||
@@ -59,9 +60,13 @@ const handler = (e) => {
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
await generateLangium();
|
||||
await mkdir('stats').catch(() => {});
|
||||
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
|
||||
await Promise.allSettled(packageNames.map((pkg) => buildPackage(pkg).catch(handler)));
|
||||
// it should build `parser` before `mermaid` because it's a dependecy
|
||||
for (const pkg of packageNames) {
|
||||
await buildPackage(pkg).catch(handler);
|
||||
}
|
||||
};
|
||||
|
||||
void main();
|
||||
|
@@ -4,7 +4,11 @@ import cors from 'cors';
|
||||
import { getBuildConfig, defaultOptions } from './util.js';
|
||||
import { context } from 'esbuild';
|
||||
import chokidar from 'chokidar';
|
||||
import { generateLangium } from '../.build/generateLangium.js';
|
||||
|
||||
const parserCtx = await context(
|
||||
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'parser' })
|
||||
);
|
||||
const mermaidCtx = await context(
|
||||
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid' })
|
||||
);
|
||||
@@ -28,7 +32,7 @@ const externalCtx = await context(
|
||||
const zenumlCtx = await context(
|
||||
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid-zenuml' })
|
||||
);
|
||||
const contexts = [mermaidCtx, mermaidIIFECtx, externalCtx, zenumlCtx];
|
||||
const contexts = [parserCtx, mermaidCtx, mermaidIIFECtx, externalCtx, zenumlCtx];
|
||||
|
||||
const rebuildAll = async () => {
|
||||
console.time('Rebuild time');
|
||||
@@ -75,10 +79,11 @@ function sendEventsToAll() {
|
||||
}
|
||||
|
||||
async function createServer() {
|
||||
await generateLangium();
|
||||
handleFileChange();
|
||||
const app = express();
|
||||
chokidar
|
||||
.watch('**/src/**/*.{js,ts,yaml,json}', {
|
||||
.watch('**/src/**/*.{js,ts,langium,yaml,json}', {
|
||||
ignoreInitial: true,
|
||||
ignored: [/node_modules/, /dist/, /docs/, /coverage/],
|
||||
})
|
||||
@@ -87,12 +92,16 @@ async function createServer() {
|
||||
if (!['add', 'change'].includes(event)) {
|
||||
return;
|
||||
}
|
||||
if (/\.langium$/.test(path)) {
|
||||
await generateLangium();
|
||||
}
|
||||
console.log(`${path} changed. Rebuilding...`);
|
||||
handleFileChange();
|
||||
});
|
||||
|
||||
app.use(cors());
|
||||
app.get('/events', eventsHandler);
|
||||
app.use(express.static('./packages/parser/dist'));
|
||||
app.use(express.static('./packages/mermaid/dist'));
|
||||
app.use(express.static('./packages/mermaid-zenuml/dist'));
|
||||
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
||||
|
@@ -6,3 +6,6 @@ cypress/plugins/index.js
|
||||
coverage
|
||||
*.json
|
||||
node_modules
|
||||
|
||||
# autogenereated by langium-cli
|
||||
generated/
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -47,3 +47,6 @@ stats/
|
||||
demos/dev/**
|
||||
!/demos/dev/example.html
|
||||
!/demos/dev/reload.js
|
||||
|
||||
# autogenereated by langium-cli
|
||||
generated/
|
||||
|
@@ -10,3 +10,6 @@ stats
|
||||
.nyc_output
|
||||
# Autogenerated by `pnpm run --filter mermaid types:build-config`
|
||||
packages/mermaid/src/config.type.ts
|
||||
|
||||
# autogenereated by langium-cli
|
||||
generated/
|
||||
|
@@ -8,6 +8,7 @@ import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import type { TemplateType } from 'rollup-plugin-visualizer/dist/plugin/template-types.js';
|
||||
import istanbul from 'vite-plugin-istanbul';
|
||||
import { packageOptions } from '../.build/common.js';
|
||||
import { generateLangium } from '../.build/generateLangium.js';
|
||||
|
||||
const visualize = process.argv.includes('--visualize');
|
||||
const watch = process.argv.includes('--watch');
|
||||
@@ -82,7 +83,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
|
||||
// @ts-expect-error According to the type definitions, rollup plugins are incompatible with vite
|
||||
typescript({ compilerOptions: { declaration: false } }),
|
||||
istanbul({
|
||||
exclude: ['node_modules', 'test/', '__mocks__'],
|
||||
exclude: ['node_modules', 'test/', '__mocks__', 'generated'],
|
||||
extension: ['.js', '.ts'],
|
||||
requireEnv: true,
|
||||
forceBuildInstrument: coverage,
|
||||
@@ -110,18 +111,24 @@ const buildPackage = async (entryName: keyof typeof packageOptions) => {
|
||||
|
||||
const main = async () => {
|
||||
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
|
||||
for (const pkg of packageNames.filter((pkg) => !mermaidOnly || pkg === 'mermaid')) {
|
||||
for (const pkg of packageNames.filter(
|
||||
(pkg) => !mermaidOnly || pkg === 'mermaid' || pkg === 'parser'
|
||||
)) {
|
||||
await buildPackage(pkg);
|
||||
}
|
||||
};
|
||||
|
||||
await generateLangium();
|
||||
|
||||
if (watch) {
|
||||
await build(getBuildConfig({ minify: false, watch, core: false, entryName: 'parser' }));
|
||||
build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' }));
|
||||
if (!mermaidOnly) {
|
||||
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
|
||||
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-zenuml' }));
|
||||
}
|
||||
} else if (visualize) {
|
||||
await build(getBuildConfig({ minify: false, watch, core: false, entryName: 'parser' }));
|
||||
await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' }));
|
||||
await build(getBuildConfig({ minify: false, core: false, entryName: 'mermaid' }));
|
||||
} else {
|
||||
|
@@ -14,6 +14,7 @@ async function createServer() {
|
||||
});
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.static('./packages/parser/dist'));
|
||||
app.use(express.static('./packages/mermaid/dist'));
|
||||
app.use(express.static('./packages/mermaid-zenuml/dist'));
|
||||
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
||||
|
@@ -74,6 +74,7 @@
|
||||
"knut",
|
||||
"knutsveidqvist",
|
||||
"laganeckas",
|
||||
"langium",
|
||||
"linetype",
|
||||
"lintstagedrc",
|
||||
"logmsg",
|
||||
|
@@ -15,11 +15,11 @@
|
||||
"git graph"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "pnpm run -r clean && pnpm build:esbuild && pnpm build:types",
|
||||
"build": "pnpm build:esbuild && pnpm build:types",
|
||||
"build:esbuild": "pnpm run -r clean && ts-node-esm --transpileOnly .esbuild/build.ts",
|
||||
"build:mermaid": "pnpm build:esbuild --mermaid",
|
||||
"build:viz": "pnpm build:esbuild --visualize",
|
||||
"build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-zenuml/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly",
|
||||
"build:types": "tsc -p ./packages/parser/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-zenuml/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly",
|
||||
"dev": "ts-node-esm --transpileOnly .esbuild/server.ts",
|
||||
"dev:vite": "ts-node-esm --transpileOnly .vite/server.ts",
|
||||
"dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev:vite",
|
||||
@@ -107,6 +107,7 @@
|
||||
"jison": "^0.4.18",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsdom": "^22.0.0",
|
||||
"langium-cli": "2.0.1",
|
||||
"lint-staged": "^13.2.1",
|
||||
"nyc": "^15.1.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
|
@@ -5,7 +5,6 @@
|
||||
* This is a dummy parser that satisfies the mermaid API logic.
|
||||
*/
|
||||
export default {
|
||||
parser: { yy: {} },
|
||||
parse: () => {
|
||||
// no op
|
||||
},
|
||||
|
@@ -72,18 +72,18 @@
|
||||
"khroma": "^2.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
"mermaid-parser": "workspace:^",
|
||||
"stylis": "^4.1.3",
|
||||
"ts-dedent": "^2.2.0",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@adobe/jsonschema2md": "^7.1.4",
|
||||
"@types/d3-scale": "^4.0.3",
|
||||
"@types/d3-scale-chromatic": "^3.0.0",
|
||||
"@types/cytoscape": "^3.19.9",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/d3-sankey": "^0.12.1",
|
||||
"@types/d3-scale": "^4.0.3",
|
||||
"@types/d3-scale-chromatic": "^3.0.0",
|
||||
"@types/d3-selection": "^3.0.5",
|
||||
"@types/d3-shape": "^3.1.1",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
|
@@ -35,7 +35,10 @@ export class Diagram {
|
||||
this.db = diagram.db;
|
||||
this.renderer = diagram.renderer;
|
||||
this.parser = diagram.parser;
|
||||
this.parser.parser.yy = this.db;
|
||||
if (this.parser.parser) {
|
||||
// The parser.parser.yy is only present in JISON parsers. So, we'll only set if required.
|
||||
this.parser.parser.yy = this.db;
|
||||
}
|
||||
this.init = diagram.init;
|
||||
this.parse();
|
||||
}
|
||||
|
@@ -61,7 +61,7 @@ export interface MermaidConfig {
|
||||
* You may also use `themeCSS` to override this value.
|
||||
*
|
||||
*/
|
||||
theme?: string | 'default' | 'forest' | 'dark' | 'neutral' | 'null';
|
||||
theme?: 'default' | 'forest' | 'dark' | 'neutral' | 'null';
|
||||
themeVariables?: any;
|
||||
themeCSS?: string;
|
||||
/**
|
||||
@@ -82,26 +82,11 @@ export interface MermaidConfig {
|
||||
* This option decides the amount of logging to be used by mermaid.
|
||||
*
|
||||
*/
|
||||
logLevel?:
|
||||
| number
|
||||
| string
|
||||
| 0
|
||||
| 2
|
||||
| 1
|
||||
| 'trace'
|
||||
| 'debug'
|
||||
| 'info'
|
||||
| 'warn'
|
||||
| 'error'
|
||||
| 'fatal'
|
||||
| 3
|
||||
| 4
|
||||
| 5
|
||||
| undefined;
|
||||
logLevel?: 'trace' | 0 | 'debug' | 1 | 'info' | 2 | 'warn' | 3 | 'error' | 4 | 'fatal' | 5;
|
||||
/**
|
||||
* Level of trust for parsed diagram
|
||||
*/
|
||||
securityLevel?: string | 'strict' | 'loose' | 'antiscript' | 'sandbox' | undefined;
|
||||
securityLevel?: 'strict' | 'loose' | 'antiscript' | 'sandbox';
|
||||
/**
|
||||
* Dictates whether mermaid starts on Page load
|
||||
*/
|
||||
@@ -723,7 +708,7 @@ export interface ErDiagramConfig extends BaseDiagramConfig {
|
||||
/**
|
||||
* Directional bias for layout of entities
|
||||
*/
|
||||
layoutDirection?: string | 'TB' | 'BT' | 'LR' | 'RL';
|
||||
layoutDirection?: 'TB' | 'BT' | 'LR' | 'RL';
|
||||
/**
|
||||
* The minimum width of an entity box. Expressed in pixels.
|
||||
*/
|
||||
@@ -788,7 +773,7 @@ export interface StateDiagramConfig extends BaseDiagramConfig {
|
||||
* Decides which rendering engine that is to be used for the rendering.
|
||||
*
|
||||
*/
|
||||
defaultRenderer?: string | 'dagre-d3' | 'dagre-wrapper' | 'elk';
|
||||
defaultRenderer?: 'dagre-d3' | 'dagre-wrapper' | 'elk';
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `MermaidConfig`'s JSON-Schema
|
||||
@@ -812,7 +797,7 @@ export interface ClassDiagramConfig extends BaseDiagramConfig {
|
||||
* Decides which rendering engine that is to be used for the rendering.
|
||||
*
|
||||
*/
|
||||
defaultRenderer?: string | 'dagre-d3' | 'dagre-wrapper' | 'elk';
|
||||
defaultRenderer?: 'dagre-d3' | 'dagre-wrapper' | 'elk';
|
||||
nodeSpacing?: number;
|
||||
rankSpacing?: number;
|
||||
/**
|
||||
@@ -872,7 +857,7 @@ export interface JourneyDiagramConfig extends BaseDiagramConfig {
|
||||
/**
|
||||
* Multiline message alignment
|
||||
*/
|
||||
messageAlign?: string | 'left' | 'center' | 'right';
|
||||
messageAlign?: 'left' | 'center' | 'right';
|
||||
/**
|
||||
* Prolongs the edge of the diagram downwards.
|
||||
*
|
||||
@@ -951,7 +936,7 @@ export interface TimelineDiagramConfig extends BaseDiagramConfig {
|
||||
/**
|
||||
* Multiline message alignment
|
||||
*/
|
||||
messageAlign?: string | 'left' | 'center' | 'right';
|
||||
messageAlign?: 'left' | 'center' | 'right';
|
||||
/**
|
||||
* Prolongs the edge of the diagram downwards.
|
||||
*
|
||||
@@ -1062,7 +1047,7 @@ export interface GanttDiagramConfig extends BaseDiagramConfig {
|
||||
* Controls the display mode.
|
||||
*
|
||||
*/
|
||||
displayMode?: string | 'compact';
|
||||
displayMode?: '' | 'compact';
|
||||
/**
|
||||
* On which day a week-based interval should start
|
||||
*
|
||||
@@ -1121,7 +1106,7 @@ export interface SequenceDiagramConfig extends BaseDiagramConfig {
|
||||
/**
|
||||
* Multiline message alignment
|
||||
*/
|
||||
messageAlign?: string | 'left' | 'center' | 'right';
|
||||
messageAlign?: 'left' | 'center' | 'right';
|
||||
/**
|
||||
* Mirror actors under diagram
|
||||
*
|
||||
@@ -1178,7 +1163,7 @@ export interface SequenceDiagramConfig extends BaseDiagramConfig {
|
||||
/**
|
||||
* This sets the text alignment of actor-attached notes
|
||||
*/
|
||||
noteAlign?: string | 'left' | 'center' | 'right';
|
||||
noteAlign?: 'left' | 'center' | 'right';
|
||||
/**
|
||||
* This sets the font size of actor messages
|
||||
*/
|
||||
@@ -1254,7 +1239,7 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
|
||||
* Defines how mermaid renders curves for flowcharts.
|
||||
*
|
||||
*/
|
||||
curve?: string | 'basis' | 'linear' | 'cardinal';
|
||||
curve?: 'basis' | 'linear' | 'cardinal';
|
||||
/**
|
||||
* Represents the padding between the labels and the shape
|
||||
*
|
||||
@@ -1266,7 +1251,7 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
|
||||
* Decides which rendering engine that is to be used for the rendering.
|
||||
*
|
||||
*/
|
||||
defaultRenderer?: string | 'dagre-d3' | 'dagre-wrapper' | 'elk';
|
||||
defaultRenderer?: 'dagre-d3' | 'dagre-wrapper' | 'elk';
|
||||
/**
|
||||
* Width of nodes where text is wrapped.
|
||||
*
|
||||
|
@@ -45,7 +45,6 @@ export const addDiagrams = () => {
|
||||
styles: {}, // should never be used
|
||||
renderer: {}, // should never be used
|
||||
parser: {
|
||||
parser: { yy: {} },
|
||||
parse: () => {
|
||||
throw new Error(
|
||||
'Diagrams beginning with --- are not valid. ' +
|
||||
|
@@ -39,7 +39,6 @@ describe('DiagramAPI', () => {
|
||||
parse: (_text) => {
|
||||
return;
|
||||
},
|
||||
parser: { yy: {} },
|
||||
},
|
||||
renderer: {},
|
||||
styles: {},
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { MermaidConfig } from '../config.type.js';
|
||||
import type { GanttDiagramConfig, MermaidConfig } from '../config.type.js';
|
||||
import { frontMatterRegex } from './regexes.js';
|
||||
// The "* as yaml" part is necessary for tree-shaking
|
||||
import * as yaml from 'js-yaml';
|
||||
@@ -6,7 +6,7 @@ import * as yaml from 'js-yaml';
|
||||
interface FrontMatterMetadata {
|
||||
title?: string;
|
||||
// Allows custom display modes. Currently used for compact mode in gantt charts.
|
||||
displayMode?: string;
|
||||
displayMode?: GanttDiagramConfig['displayMode'];
|
||||
config?: MermaidConfig;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export function extractFrontMatter(text: string): FrontMatterResult {
|
||||
|
||||
// Only add properties that are explicitly supported, if they exist
|
||||
if (parsed.displayMode) {
|
||||
metadata.displayMode = parsed.displayMode.toString();
|
||||
metadata.displayMode = parsed.displayMode.toString() as GanttDiagramConfig['displayMode'];
|
||||
}
|
||||
if (parsed.title) {
|
||||
metadata.title = parsed.title.toString();
|
||||
|
@@ -88,7 +88,7 @@ export type DrawDefinition = (
|
||||
|
||||
export interface ParserDefinition {
|
||||
parse: (text: string) => void;
|
||||
parser: { yy: DiagramDB };
|
||||
parser?: { yy: DiagramDB };
|
||||
}
|
||||
|
||||
export type HTML = d3.Selection<HTMLIFrameElement, unknown, Element | null, unknown>;
|
||||
|
@@ -30,9 +30,6 @@ describe('diagram detection', () => {
|
||||
parse: () => {
|
||||
// no-op
|
||||
},
|
||||
parser: {
|
||||
yy: {},
|
||||
},
|
||||
},
|
||||
renderer: {},
|
||||
styles: {},
|
||||
|
15
packages/mermaid/src/diagrams/common/populateCommonDb.ts
Normal file
15
packages/mermaid/src/diagrams/common/populateCommonDb.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { DiagramAST } from 'mermaid-parser';
|
||||
|
||||
import type { DiagramDB } from '../../diagram-api/types.js';
|
||||
|
||||
export function populateCommonDb(ast: DiagramAST, db: DiagramDB) {
|
||||
if (ast.accDescr) {
|
||||
db.setAccDescription?.(ast.accDescr);
|
||||
}
|
||||
if (ast.accTitle) {
|
||||
db.setAccTitle?.(ast.accTitle);
|
||||
}
|
||||
if (ast.title) {
|
||||
db.setDiagramTitle?.(ast.title);
|
||||
}
|
||||
}
|
@@ -5,7 +5,6 @@ export const diagram: DiagramDefinition = {
|
||||
db: {},
|
||||
renderer,
|
||||
parser: {
|
||||
parser: { yy: {} },
|
||||
parse: (): void => {
|
||||
return;
|
||||
},
|
||||
|
@@ -1,24 +1,31 @@
|
||||
// @ts-ignore - jison doesn't export types
|
||||
import { parser } from './parser/info.jison';
|
||||
import { db } from './infoDb.js';
|
||||
|
||||
describe('info diagram', () => {
|
||||
beforeEach(() => {
|
||||
parser.yy = db;
|
||||
parser.yy.clear();
|
||||
});
|
||||
import { parser } from './infoParser.js';
|
||||
|
||||
describe('info', () => {
|
||||
it('should handle an info definition', () => {
|
||||
const str = `info`;
|
||||
parser.parse(str);
|
||||
|
||||
expect(db.getInfo()).toBeFalsy();
|
||||
expect(() => {
|
||||
parser.parse(str);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle an info definition with showInfo', () => {
|
||||
const str = `info showInfo`;
|
||||
parser.parse(str);
|
||||
expect(() => {
|
||||
parser.parse(str);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
expect(db.getInfo()).toBeTruthy();
|
||||
it('should throw because of unsupported info grammar', () => {
|
||||
const str = `info unsupported`;
|
||||
expect(() => {
|
||||
parser.parse(str);
|
||||
}).toThrow('Parsing failed: unexpected character: ->u<- at offset: 5, skipped 11 characters.');
|
||||
});
|
||||
|
||||
it('should throw because of unsupported info grammar', () => {
|
||||
const str = `info unsupported`;
|
||||
expect(() => {
|
||||
parser.parse(str);
|
||||
}).toThrow('Parsing failed: unexpected character: ->u<- at offset: 5, skipped 11 characters.');
|
||||
});
|
||||
});
|
||||
|
@@ -1,23 +1,10 @@
|
||||
import type { InfoFields, InfoDB } from './infoTypes.js';
|
||||
import { version } from '../../../package.json';
|
||||
|
||||
export const DEFAULT_INFO_DB: InfoFields = {
|
||||
info: false,
|
||||
} as const;
|
||||
export const DEFAULT_INFO_DB: InfoFields = { version } as const;
|
||||
|
||||
let info: boolean = DEFAULT_INFO_DB.info;
|
||||
|
||||
export const setInfo = (toggle: boolean): void => {
|
||||
info = toggle;
|
||||
};
|
||||
|
||||
export const getInfo = (): boolean => info;
|
||||
|
||||
const clear = (): void => {
|
||||
info = DEFAULT_INFO_DB.info;
|
||||
};
|
||||
export const getVersion = (): string => DEFAULT_INFO_DB.version;
|
||||
|
||||
export const db: InfoDB = {
|
||||
clear,
|
||||
setInfo,
|
||||
getInfo,
|
||||
getVersion,
|
||||
};
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
// @ts-ignore - jison doesn't export types
|
||||
import parser from './parser/info.jison';
|
||||
import { parser } from './infoParser.js';
|
||||
import { db } from './infoDb.js';
|
||||
import { renderer } from './infoRenderer.js';
|
||||
|
||||
|
12
packages/mermaid/src/diagrams/info/infoParser.ts
Normal file
12
packages/mermaid/src/diagrams/info/infoParser.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { Info } from 'mermaid-parser';
|
||||
import { parse } from 'mermaid-parser';
|
||||
|
||||
import { log } from '../../logger.js';
|
||||
import type { ParserDefinition } from '../../diagram-api/types.js';
|
||||
|
||||
export const parser: ParserDefinition = {
|
||||
parse: (input: string): void => {
|
||||
const ast: Info = parse('info', input);
|
||||
log.debug(ast);
|
||||
},
|
||||
};
|
@@ -1,11 +1,9 @@
|
||||
import type { DiagramDB } from '../../diagram-api/types.js';
|
||||
|
||||
export interface InfoFields {
|
||||
info: boolean;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface InfoDB extends DiagramDB {
|
||||
clear: () => void;
|
||||
setInfo: (info: boolean) => void;
|
||||
getInfo: () => boolean;
|
||||
getVersion: () => string;
|
||||
}
|
||||
|
@@ -1,48 +0,0 @@
|
||||
/** mermaid
|
||||
* https://knsv.github.io/mermaid
|
||||
* (c) 2015 Knut Sveidqvist
|
||||
* MIT license.
|
||||
*/
|
||||
%lex
|
||||
|
||||
%options case-insensitive
|
||||
|
||||
%{
|
||||
// Pre-lexer code can go here
|
||||
%}
|
||||
|
||||
%%
|
||||
|
||||
"info" return 'info' ;
|
||||
[\s\n\r]+ return 'NL' ;
|
||||
[\s]+ return 'space';
|
||||
"showInfo" return 'showInfo';
|
||||
<<EOF>> return 'EOF' ;
|
||||
. return 'TXT' ;
|
||||
|
||||
/lex
|
||||
|
||||
%start start
|
||||
|
||||
%% /* language grammar */
|
||||
|
||||
start
|
||||
// %{ : info document 'EOF' { return yy; } }
|
||||
: info document 'EOF' { return yy; }
|
||||
;
|
||||
|
||||
document
|
||||
: /* empty */
|
||||
| document line
|
||||
;
|
||||
|
||||
line
|
||||
: statement { }
|
||||
| 'NL'
|
||||
;
|
||||
|
||||
statement
|
||||
: showInfo { yy.setInfo(true); }
|
||||
;
|
||||
|
||||
%%
|
@@ -102,7 +102,6 @@ describe('when using mermaid and ', () => {
|
||||
parse: (_text) => {
|
||||
return;
|
||||
},
|
||||
parser: { yy: {} },
|
||||
},
|
||||
styles: () => {
|
||||
// do nothing
|
||||
|
@@ -563,7 +563,7 @@ describe('mermaidAPI', () => {
|
||||
const config = {
|
||||
logLevel: 0,
|
||||
securityLevel: 'loose',
|
||||
};
|
||||
} as const;
|
||||
mermaidAPI.initialize(config);
|
||||
mermaidAPI.setConfig({ securityLevel: 'strict', logLevel: 1 });
|
||||
expect(mermaidAPI.getConfig().logLevel).toBe(1);
|
||||
|
@@ -63,8 +63,6 @@ properties:
|
||||
meta:enum:
|
||||
'null': Can be set to disable any pre-defined mermaid theme
|
||||
default: 'default'
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "default" | "forest" | "dark" | "neutral" | "null"'
|
||||
themeVariables:
|
||||
tsType: any
|
||||
themeCSS:
|
||||
@@ -115,8 +113,6 @@ properties:
|
||||
error: Equivalent to 4
|
||||
fatal: Equivalent to 5 (default)
|
||||
default: 5
|
||||
# Allow any number or string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'number | string | 0 | 2 | 1 | "trace" | "debug" | "info" | "warn" | "error" | "fatal" | 3 | 4 | 5 | undefined'
|
||||
securityLevel:
|
||||
description: Level of trust for parsed diagram
|
||||
type: string
|
||||
@@ -134,8 +130,6 @@ properties:
|
||||
This prevent any JavaScript from running in the context.
|
||||
This may hinder interactive functionality of the diagram, like scripts, popups in the sequence diagram, or links to other tabs or targets, etc.
|
||||
default: strict
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "strict" | "loose" | "antiscript" | "sandbox" | undefined'
|
||||
startOnLoad:
|
||||
description: Dictates whether mermaid starts on Page load
|
||||
type: boolean
|
||||
@@ -1021,8 +1015,6 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file)
|
||||
LR: Left-Right
|
||||
RL: Right to Left
|
||||
default: TB
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "TB" | "BT" | "LR" | "RL"'
|
||||
minEntityWidth:
|
||||
description: The minimum width of an entity box. Expressed in pixels.
|
||||
type: integer
|
||||
@@ -1135,8 +1127,6 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file)
|
||||
dagre-d3: The [dagre-d3-es](https://www.npmjs.com/package/dagre-d3-es) library.
|
||||
dagre-wrapper: wrapper for dagre implemented in mermaid
|
||||
elk: Layout using [elkjs](https://github.com/kieler/elkjs)
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "dagre-d3" | "dagre-wrapper" | "elk"'
|
||||
|
||||
ClassDiagramConfig:
|
||||
title: Class Diagram Config
|
||||
@@ -1252,8 +1242,6 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file)
|
||||
- center
|
||||
- right
|
||||
default: center
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "left" | "center" | "right"'
|
||||
bottomMarginAdj:
|
||||
description: |
|
||||
Prolongs the edge of the diagram downwards.
|
||||
@@ -1378,8 +1366,6 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file)
|
||||
- center
|
||||
- right
|
||||
default: center
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "left" | "center" | "right"'
|
||||
bottomMarginAdj:
|
||||
description: |
|
||||
Prolongs the edge of the diagram downwards.
|
||||
@@ -1543,13 +1529,10 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file)
|
||||
meta:enum:
|
||||
compact: Enables displaying multiple tasks on the same row.
|
||||
default: ''
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "compact"'
|
||||
weekday:
|
||||
description: |
|
||||
On which day a week-based interval should start
|
||||
type: string
|
||||
tsType: '"monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday"'
|
||||
enum:
|
||||
- monday
|
||||
- tuesday
|
||||
@@ -1691,8 +1674,6 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file)
|
||||
type: string
|
||||
enum: ['left', 'center', 'right']
|
||||
default: 'center'
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "left" | "center" | "right"'
|
||||
|
||||
messageFontSize:
|
||||
description: This sets the font size of actor messages
|
||||
@@ -1780,8 +1761,6 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file)
|
||||
type: string
|
||||
enum: ['basis', 'linear', 'cardinal']
|
||||
default: 'basis'
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "basis" | "linear" | "cardinal"'
|
||||
padding:
|
||||
description: |
|
||||
Represents the padding between the labels and the shape
|
||||
|
@@ -286,56 +286,46 @@ describe('when formatting urls', function () {
|
||||
it('should handle links', function () {
|
||||
const url = 'https://mermaid-js.github.io/mermaid/#/';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
let result = utils.formatUrl(url, { securityLevel: 'loose' });
|
||||
expect(result).toEqual(url);
|
||||
|
||||
config.securityLevel = 'strict';
|
||||
result = utils.formatUrl(url, config);
|
||||
result = utils.formatUrl(url, { securityLevel: 'strict' });
|
||||
expect(result).toEqual(url);
|
||||
});
|
||||
it('should handle anchors', function () {
|
||||
const url = '#interaction';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
let result = utils.formatUrl(url, { securityLevel: 'loose' });
|
||||
expect(result).toEqual(url);
|
||||
|
||||
config.securityLevel = 'strict';
|
||||
result = utils.formatUrl(url, config);
|
||||
result = utils.formatUrl(url, { securityLevel: 'strict' });
|
||||
expect(result).toEqual(url);
|
||||
});
|
||||
it('should handle mailto', function () {
|
||||
const url = 'mailto:user@user.user';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
let result = utils.formatUrl(url, { securityLevel: 'loose' });
|
||||
expect(result).toEqual(url);
|
||||
|
||||
config.securityLevel = 'strict';
|
||||
result = utils.formatUrl(url, config);
|
||||
result = utils.formatUrl(url, { securityLevel: 'strict' });
|
||||
expect(result).toEqual(url);
|
||||
});
|
||||
it('should handle other protocols', function () {
|
||||
const url = 'notes://do-your-thing/id';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
let result = utils.formatUrl(url, { securityLevel: 'loose' });
|
||||
expect(result).toEqual(url);
|
||||
|
||||
config.securityLevel = 'strict';
|
||||
result = utils.formatUrl(url, config);
|
||||
result = utils.formatUrl(url, { securityLevel: 'strict' });
|
||||
expect(result).toEqual(url);
|
||||
});
|
||||
it('should handle scripts', function () {
|
||||
const url = 'javascript:alert("test")';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
let result = utils.formatUrl(url, { securityLevel: 'loose' });
|
||||
expect(result).toEqual(url);
|
||||
|
||||
config.securityLevel = 'strict';
|
||||
result = utils.formatUrl(url, config);
|
||||
result = utils.formatUrl(url, { securityLevel: 'strict' });
|
||||
expect(result).toEqual('about:blank');
|
||||
});
|
||||
});
|
||||
|
21
packages/parser/LICENSE
Normal file
21
packages/parser/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2023 Yokozuna59
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
63
packages/parser/README.md
Normal file
63
packages/parser/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/mermaid-js/mermaid/develop/docs/public/favicon.svg" height="150">
|
||||
|
||||
</p>
|
||||
<h1 align="center">
|
||||
Mermaid Parser
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
Mermaid parser package
|
||||
<p>
|
||||
|
||||
[](https://www.npmjs.com/package/mermaid-parser)
|
||||
|
||||
## How the package works
|
||||
|
||||
The package exports a `parse` function that has two parameters:
|
||||
|
||||
```ts
|
||||
declare function parse<T extends DiagramAST>(
|
||||
diagramType: keyof typeof initializers,
|
||||
text: string
|
||||
): T;
|
||||
```
|
||||
|
||||
## How does a Langium-based parser work?
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor Package
|
||||
participant Module
|
||||
participant TokenBuilder
|
||||
participant Lexer
|
||||
participant Parser
|
||||
participant ValueConverter
|
||||
|
||||
|
||||
Package ->> Module: Create services
|
||||
Module ->> TokenBuilder: Override or/and<br>reorder rules
|
||||
TokenBuilder ->> Lexer: Read the string and transform<br>it into a token stream
|
||||
Lexer ->> Parser: Parse token<br>stream into AST
|
||||
Parser ->> ValueConverter: Clean/modify tokenized<br>rules returned value
|
||||
ValueConverter -->> Package: Return AST
|
||||
```
|
||||
|
||||
- When to override `TokenBuilder`?
|
||||
|
||||
- To override keyword rules.
|
||||
- To override terminal rules that need a custom function.
|
||||
- To manually reorder the list of rules.
|
||||
|
||||
- When to override `Lexer`?
|
||||
|
||||
- To modify input before tokenizing.
|
||||
- To insert/modify tokens that cannot or have not been parsed.
|
||||
|
||||
- When to override `LangiumParser`?
|
||||
|
||||
- To insert or modify attributes that can't be parsed.
|
||||
|
||||
- When to override `ValueConverter`?
|
||||
|
||||
- To modify the returned value from the parser.
|
13
packages/parser/langium-config.json
Normal file
13
packages/parser/langium-config.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"projectName": "Mermaid",
|
||||
"languages": [
|
||||
{
|
||||
"id": "info",
|
||||
"grammar": "src/language/info/info.langium",
|
||||
"fileExtensions": [".mmd", ".mermaid"]
|
||||
}
|
||||
],
|
||||
"mode": "production",
|
||||
"importExtension": ".js",
|
||||
"out": "src/language/generated"
|
||||
}
|
48
packages/parser/package.json
Normal file
48
packages/parser/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "mermaid-parser",
|
||||
"version": "0.2.0",
|
||||
"description": "MermaidJS parser",
|
||||
"author": "Yokozuna59",
|
||||
"contributors": [
|
||||
"Yokozuna59",
|
||||
"Sidharth Vinod (https://sidharth.dev)"
|
||||
],
|
||||
"homepage": "https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid/parser/#readme",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/mermaid-parser.esm.mjs",
|
||||
"types": "./dist/src/index.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf dist src/language/generated",
|
||||
"langium:generate": "langium generate",
|
||||
"langium:watch": "langium generate --watch",
|
||||
"prepublishOnly": "pnpm -w run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mermaid-js/mermaid.git",
|
||||
"directory": "packages/parser"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"mermaid",
|
||||
"parser",
|
||||
"ast"
|
||||
],
|
||||
"dependencies": {
|
||||
"langium": "2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"langium-cli": "2.0.1"
|
||||
},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
3
packages/parser/src/index.ts
Normal file
3
packages/parser/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export type { Info } from './language/index.js';
|
||||
export type { DiagramAST } from './parse.js';
|
||||
export { parse, MermaidParseError } from './parse.js';
|
51
packages/parser/src/language/chevrotainWrapper.ts
Normal file
51
packages/parser/src/language/chevrotainWrapper.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
type CustomPatternMatcherReturn = [string] & { payload?: any };
|
||||
|
||||
export type CustomPatternMatcherFunc = (
|
||||
text: string,
|
||||
offset: number,
|
||||
tokens: IToken[],
|
||||
groups: {
|
||||
[groupName: string]: IToken[];
|
||||
}
|
||||
) => CustomPatternMatcherReturn | RegExpExecArray | null;
|
||||
|
||||
interface ICustomPattern {
|
||||
exec: CustomPatternMatcherFunc;
|
||||
}
|
||||
|
||||
type TokenPattern = RegExp | string | CustomPatternMatcherFunc | ICustomPattern;
|
||||
|
||||
export interface IToken {
|
||||
image: string;
|
||||
startOffset: number;
|
||||
startLine?: number;
|
||||
startColumn?: number;
|
||||
endOffset?: number;
|
||||
endLine?: number;
|
||||
endColumn?: number;
|
||||
isInsertedInRecovery?: boolean;
|
||||
tokenTypeIdx: number;
|
||||
tokenType: TokenType;
|
||||
payload?: any;
|
||||
}
|
||||
|
||||
export interface TokenType {
|
||||
name: string;
|
||||
GROUP?: string;
|
||||
PATTERN?: TokenPattern;
|
||||
LABEL?: string;
|
||||
LONGER_ALT?: TokenType | TokenType[];
|
||||
POP_MODE?: boolean;
|
||||
PUSH_MODE?: string;
|
||||
LINE_BREAKS?: boolean;
|
||||
CATEGORIES?: TokenType[];
|
||||
tokenTypeIdx?: number;
|
||||
categoryMatches?: number[];
|
||||
categoryMatchesMap?: {
|
||||
[tokType: number]: boolean;
|
||||
};
|
||||
isParent?: boolean;
|
||||
START_CHARS_HINT?: (string | number)[];
|
||||
}
|
14
packages/parser/src/language/common/common.langium
Normal file
14
packages/parser/src/language/common/common.langium
Normal file
@@ -0,0 +1,14 @@
|
||||
fragment TitleAndAccessibilities:
|
||||
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) NEWLINE+)+
|
||||
;
|
||||
|
||||
terminal NEWLINE: /\r?\n/;
|
||||
terminal ACC_DESCR: /accDescr(?:[\t ]*:[\t ]*[^\n\r]*?(?=%%)|\s*{[^}]*})|accDescr(?:[\t ]*:[\t ]*[^\n\r]*|\s*{[^}]*})/;
|
||||
terminal ACC_TITLE: /accTitle[\t ]*:[\t ]*[^\n\r]*?(?=%%)|accTitle[\t ]*:[\t ]*[^\n\r]*/;
|
||||
terminal TITLE: /title(?:[\t ]+[^\n\r]*?|)(?=%%)|title(?:[\t ]+[^\n\r]*|)/;
|
||||
|
||||
hidden terminal WHITESPACE: /[\t ]+/;
|
||||
// TODO: add YAML_COMMENT hidden rule without interfere actual grammar
|
||||
hidden terminal YAML: /---[\t ]*\r?\n[\S\s]*?---[\t ]*(?!.)/;
|
||||
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%\s*/;
|
||||
hidden terminal SINGLE_LINE_COMMENT: /[\t ]*%%[^\n\r]*/;
|
8
packages/parser/src/language/common/commonLexer.ts
Normal file
8
packages/parser/src/language/common/commonLexer.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { LexerResult } from 'langium';
|
||||
import { DefaultLexer } from 'langium';
|
||||
|
||||
export class CommonLexer extends DefaultLexer {
|
||||
public override tokenize(text: string): LexerResult {
|
||||
return super.tokenize(text + '\n');
|
||||
}
|
||||
}
|
14
packages/parser/src/language/common/commonMatcher.ts
Normal file
14
packages/parser/src/language/common/commonMatcher.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Matches single and multiline accessible description
|
||||
*/
|
||||
export const accessibilityDescrRegex = /accDescr(?:[\t ]*:[\t ]*([^\n\r]*)|\s*{([^}]*)})/;
|
||||
|
||||
/**
|
||||
* Matches single line accessible title
|
||||
*/
|
||||
export const accessibilityTitleRegex = /accTitle[\t ]*:[\t ]*([^\n\r]*)/;
|
||||
|
||||
/**
|
||||
* Matches a single line title
|
||||
*/
|
||||
export const titleRegex = /title([\t ]+([^\n\r]*)|)/;
|
74
packages/parser/src/language/common/commonValueConverters.ts
Normal file
74
packages/parser/src/language/common/commonValueConverters.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { CstNode, GrammarAST, ValueType } from 'langium';
|
||||
import { DefaultValueConverter } from 'langium';
|
||||
|
||||
import { accessibilityDescrRegex, accessibilityTitleRegex, titleRegex } from './commonMatcher.js';
|
||||
|
||||
export class CommonValueConverter extends DefaultValueConverter {
|
||||
protected override runConverter(
|
||||
rule: GrammarAST.AbstractRule,
|
||||
input: string,
|
||||
cstNode: CstNode
|
||||
): ValueType {
|
||||
const value: ValueType | undefined = CommonValueConverter.customRunConverter(
|
||||
rule,
|
||||
input,
|
||||
cstNode
|
||||
);
|
||||
if (value === undefined) {
|
||||
return super.runConverter(rule, input, cstNode);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method contains convert logic to be used by class itself or `MermaidValueConverter`.
|
||||
*
|
||||
* @param rule - Parsed rule.
|
||||
* @param input - Matched string.
|
||||
* @param _cstNode - Node in the Concrete Syntax Tree (CST).
|
||||
* @returns converted the value if it's common rule or `undefined` if it's not.
|
||||
*/
|
||||
public static customRunConverter(
|
||||
rule: GrammarAST.AbstractRule,
|
||||
input: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_cstNode: CstNode
|
||||
): ValueType | undefined {
|
||||
let regex: RegExp | undefined;
|
||||
switch (rule.name) {
|
||||
case 'ACC_DESCR': {
|
||||
regex = new RegExp(accessibilityDescrRegex.source);
|
||||
break;
|
||||
}
|
||||
case 'ACC_TITLE': {
|
||||
regex = new RegExp(accessibilityTitleRegex.source);
|
||||
break;
|
||||
}
|
||||
case 'TITLE': {
|
||||
regex = new RegExp(titleRegex.source);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (regex === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const match = regex.exec(input);
|
||||
if (match === null) {
|
||||
return undefined;
|
||||
}
|
||||
// single line title, accTitle, accDescr
|
||||
if (match[1] !== undefined) {
|
||||
return match[1].trim().replaceAll(/[\t ]{2,}/gm, ' ');
|
||||
}
|
||||
// multi line accDescr
|
||||
if (match[2] !== undefined) {
|
||||
return match[2]
|
||||
.replaceAll(/^\s*/gm, '')
|
||||
.replaceAll(/\s+$/gm, '')
|
||||
.replaceAll(/[\t ]{2,}/gm, ' ')
|
||||
.replaceAll(/[\n\r]{2,}/gm, '\n');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
2
packages/parser/src/language/common/index.ts
Normal file
2
packages/parser/src/language/common/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './commonLexer.js';
|
||||
export * from './commonValueConverters.js';
|
6
packages/parser/src/language/index.ts
Normal file
6
packages/parser/src/language/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './generated/ast.js';
|
||||
export * from './generated/grammar.js';
|
||||
export * from './generated/module.js';
|
||||
|
||||
export * from './common/index.js';
|
||||
export * from './info/index.js';
|
1
packages/parser/src/language/info/index.ts
Normal file
1
packages/parser/src/language/info/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './infoModule.js';
|
9
packages/parser/src/language/info/info.langium
Normal file
9
packages/parser/src/language/info/info.langium
Normal file
@@ -0,0 +1,9 @@
|
||||
grammar Info
|
||||
import "../common/common";
|
||||
|
||||
entry Info:
|
||||
NEWLINE*
|
||||
"info" NEWLINE*
|
||||
("showInfo" NEWLINE*)?
|
||||
TitleAndAccessibilities?
|
||||
;
|
72
packages/parser/src/language/info/infoModule.ts
Normal file
72
packages/parser/src/language/info/infoModule.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type {
|
||||
DefaultSharedModuleContext,
|
||||
LangiumServices,
|
||||
LangiumSharedServices,
|
||||
Module,
|
||||
PartialLangiumServices,
|
||||
} from 'langium';
|
||||
import { EmptyFileSystem, createDefaultModule, createDefaultSharedModule, inject } from 'langium';
|
||||
|
||||
import { MermaidGeneratedSharedModule, InfoGeneratedModule } from '../generated/module.js';
|
||||
import { CommonLexer } from '../common/commonLexer.js';
|
||||
import { CommonValueConverter } from '../common/commonValueConverters.js';
|
||||
import { InfoTokenBuilder } from './infoTokenBuilder.js';
|
||||
|
||||
/**
|
||||
* Declaration of `Info` services.
|
||||
*/
|
||||
type InfoAddedServices = {
|
||||
parser: {
|
||||
Lexer: CommonLexer;
|
||||
TokenBuilder: InfoTokenBuilder;
|
||||
ValueConverter: CommonValueConverter;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Union of Langium default services and `Info` services.
|
||||
*/
|
||||
export type InfoServices = LangiumServices & InfoAddedServices;
|
||||
|
||||
/**
|
||||
* Dependency injection module that overrides Langium default services and
|
||||
* contributes the declared `Info` services.
|
||||
*/
|
||||
export const InfoModule: Module<InfoServices, PartialLangiumServices & InfoAddedServices> = {
|
||||
parser: {
|
||||
Lexer: (services) => new CommonLexer(services),
|
||||
TokenBuilder: () => new InfoTokenBuilder(),
|
||||
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 createInfoServices(context: DefaultSharedModuleContext = EmptyFileSystem): {
|
||||
shared: LangiumSharedServices;
|
||||
Info: InfoServices;
|
||||
} {
|
||||
const shared: LangiumSharedServices = inject(
|
||||
createDefaultSharedModule(context),
|
||||
MermaidGeneratedSharedModule
|
||||
);
|
||||
const Info: InfoServices = inject(
|
||||
createDefaultModule({ shared }),
|
||||
InfoGeneratedModule,
|
||||
InfoModule
|
||||
);
|
||||
shared.ServiceRegistry.register(Info);
|
||||
return { shared, Info };
|
||||
}
|
24
packages/parser/src/language/info/infoTokenBuilder.ts
Normal file
24
packages/parser/src/language/info/infoTokenBuilder.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { GrammarAST, Stream, TokenBuilderOptions } from 'langium';
|
||||
import { DefaultTokenBuilder } from 'langium';
|
||||
|
||||
import type { TokenType } from '../chevrotainWrapper.js';
|
||||
|
||||
export class InfoTokenBuilder extends DefaultTokenBuilder {
|
||||
protected override buildKeywordTokens(
|
||||
rules: Stream<GrammarAST.AbstractRule>,
|
||||
terminalTokens: TokenType[],
|
||||
options?: TokenBuilderOptions
|
||||
): TokenType[] {
|
||||
const tokenTypes: TokenType[] = super.buildKeywordTokens(rules, terminalTokens, options);
|
||||
// to restrict users, they mustn't have any non-whitespace characters after the keyword.
|
||||
tokenTypes.forEach((tokenType: TokenType): void => {
|
||||
if (
|
||||
(tokenType.name === 'info' || tokenType.name === 'showInfo') &&
|
||||
tokenType.PATTERN !== undefined
|
||||
) {
|
||||
tokenType.PATTERN = new RegExp(tokenType.PATTERN.toString() + '(?!\\S)');
|
||||
}
|
||||
});
|
||||
return tokenTypes;
|
||||
}
|
||||
}
|
43
packages/parser/src/parse.ts
Normal file
43
packages/parser/src/parse.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { LangiumParser, ParseResult } from 'langium';
|
||||
import type { Info } from './index.js';
|
||||
import { createInfoServices } from './language/index.js';
|
||||
|
||||
export type DiagramAST = Info;
|
||||
|
||||
const parsers: Record<string, LangiumParser> = {};
|
||||
|
||||
const initializers = {
|
||||
info: () => {
|
||||
// Will have to make parse async to use this. Can try later...
|
||||
// const { createInfoServices } = await import('./language/info/index.js');
|
||||
const parser = createInfoServices().Info.parser.LangiumParser;
|
||||
parsers['info'] = parser;
|
||||
},
|
||||
} as const;
|
||||
export function parse(diagramType: 'info', text: string): Info;
|
||||
export function parse<T extends DiagramAST>(
|
||||
diagramType: keyof typeof initializers,
|
||||
text: string
|
||||
): T {
|
||||
const initializer = initializers[diagramType];
|
||||
if (!initializer) {
|
||||
throw new Error(`Unknown diagram type: ${diagramType}`);
|
||||
}
|
||||
if (!parsers[diagramType]) {
|
||||
initializer();
|
||||
}
|
||||
const parser: LangiumParser = parsers[diagramType];
|
||||
const result: ParseResult<T> = parser.parse<T>(text);
|
||||
if (result.lexerErrors.length > 0 || result.parserErrors.length > 0) {
|
||||
throw new MermaidParseError(result);
|
||||
}
|
||||
return result.value;
|
||||
}
|
||||
|
||||
export class MermaidParseError extends Error {
|
||||
constructor(public result: ParseResult<DiagramAST>) {
|
||||
const lexerErrors: string = result.lexerErrors.map((err) => err.message).join('\n');
|
||||
const parserErrors: string = result.parserErrors.map((err) => err.message).join('\n');
|
||||
super(`Parsing failed: ${lexerErrors} ${parserErrors}`);
|
||||
}
|
||||
}
|
57
packages/parser/tests/info.test.ts
Normal file
57
packages/parser/tests/info.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { LangiumParser, ParseResult } from 'langium';
|
||||
|
||||
import type { InfoServices } from '../src/language/index.js';
|
||||
import { Info, createInfoServices } from '../src/language/index.js';
|
||||
import { noErrorsOrAlternatives } from './test-util.js';
|
||||
|
||||
const services: InfoServices = createInfoServices().Info;
|
||||
const parser: LangiumParser = services.parser.LangiumParser;
|
||||
function createInfoTestServices(): {
|
||||
services: InfoServices;
|
||||
parse: (input: string) => ParseResult<Info>;
|
||||
} {
|
||||
const parse = (input: string) => {
|
||||
return parser.parse<Info>(input);
|
||||
};
|
||||
|
||||
return { services, parse };
|
||||
}
|
||||
|
||||
describe('info', () => {
|
||||
const { parse } = createInfoTestServices();
|
||||
|
||||
it.each([
|
||||
`info`,
|
||||
`
|
||||
info`,
|
||||
`info
|
||||
`,
|
||||
`
|
||||
info
|
||||
`,
|
||||
])('should handle empty info', (context: string) => {
|
||||
const result = parse(context);
|
||||
noErrorsOrAlternatives(result);
|
||||
|
||||
expect(result.value.$type).toBe(Info);
|
||||
});
|
||||
|
||||
it.each([
|
||||
`info showInfo`,
|
||||
`
|
||||
info showInfo`,
|
||||
`info
|
||||
showInfo
|
||||
`,
|
||||
`
|
||||
info
|
||||
showInfo
|
||||
`,
|
||||
])('should handle showInfo', (context: string) => {
|
||||
const result = parse(context);
|
||||
noErrorsOrAlternatives(result);
|
||||
|
||||
expect(result.value.$type).toBe(Info);
|
||||
});
|
||||
});
|
18
packages/parser/tests/test-util.ts
Normal file
18
packages/parser/tests/test-util.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { expect, vi } from 'vitest';
|
||||
import type { ParseResult } from 'langium';
|
||||
|
||||
const consoleMock = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||
|
||||
/**
|
||||
* A helper test function that validate that the result doesn't have errors
|
||||
* or any ambiguous alternatives from chevrotain.
|
||||
*
|
||||
* @param result - the result `parse` function.
|
||||
*/
|
||||
export function noErrorsOrAlternatives(result: ParseResult) {
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
|
||||
expect(consoleMock).not.toHaveBeenCalled();
|
||||
consoleMock.mockReset();
|
||||
}
|
12
packages/parser/tsconfig.json
Normal file
12
packages/parser/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "./dist",
|
||||
"allowJs": false,
|
||||
"preserveSymlinks": false,
|
||||
"strictPropertyInitialization": false
|
||||
},
|
||||
"include": ["./src/**/*.ts", "./tests/**/*.ts"],
|
||||
"typeRoots": ["./src/types"]
|
||||
}
|
6585
pnpm-lock.yaml
generated
6585
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Fail on errors
|
||||
set -euxo pipefail
|
||||
|
||||
# Increase heap size
|
||||
export NODE_OPTIONS="--max_old_space_size=4096"
|
||||
|
||||
# Build mermaid
|
||||
pnpm build
|
||||
|
||||
# Clone the Mermaid Live Editor repository
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import jison from './.vite/jisonPlugin.js';
|
||||
import jsonSchemaPlugin from './.vite/jsonSchemaPlugin.js';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { defaultExclude, defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
@@ -22,7 +22,7 @@ export default defineConfig({
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'json', 'html', 'lcov'],
|
||||
reportsDirectory: './coverage/vitest',
|
||||
exclude: ['**/node_modules/**', '**/tests/**', '**/__mocks__/**'],
|
||||
exclude: [...defaultExclude, './tests/**', '**/__mocks__/**', '**/generated/'],
|
||||
},
|
||||
},
|
||||
build: {
|
||||
|
Reference in New Issue
Block a user