mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
Merge pull request #4727 from Yokozuna59/add-info-langium-parser
feat: add `@mermaid-js/parser` package and `info` langium parser
This commit is contained in:
commit
44b93c039a
@ -2,6 +2,11 @@
|
|||||||
* Shared common options for both ESBuild and Vite
|
* Shared common options for both ESBuild and Vite
|
||||||
*/
|
*/
|
||||||
export const packageOptions = {
|
export const packageOptions = {
|
||||||
|
parser: {
|
||||||
|
name: 'mermaid-parser',
|
||||||
|
packageName: 'parser',
|
||||||
|
file: 'index.ts',
|
||||||
|
},
|
||||||
mermaid: {
|
mermaid: {
|
||||||
name: 'mermaid',
|
name: 'mermaid',
|
||||||
packageName: '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 { mkdir, writeFile } from 'node:fs/promises';
|
||||||
import { MermaidBuildOptions, defaultOptions, getBuildConfig } from './util.js';
|
import { MermaidBuildOptions, defaultOptions, getBuildConfig } from './util.js';
|
||||||
import { packageOptions } from '../.build/common.js';
|
import { packageOptions } from '../.build/common.js';
|
||||||
|
import { generateLangium } from '../.build/generateLangium.js';
|
||||||
|
|
||||||
const shouldVisualize = process.argv.includes('--visualize');
|
const shouldVisualize = process.argv.includes('--visualize');
|
||||||
|
|
||||||
@ -52,9 +53,13 @@ const handler = (e) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
|
await generateLangium();
|
||||||
await mkdir('stats').catch(() => {});
|
await mkdir('stats').catch(() => {});
|
||||||
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
|
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();
|
void main();
|
||||||
|
@ -4,7 +4,11 @@ import cors from 'cors';
|
|||||||
import { getBuildConfig, defaultOptions } from './util.js';
|
import { getBuildConfig, defaultOptions } from './util.js';
|
||||||
import { context } from 'esbuild';
|
import { context } from 'esbuild';
|
||||||
import chokidar from 'chokidar';
|
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(
|
const mermaidCtx = await context(
|
||||||
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid' })
|
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid' })
|
||||||
);
|
);
|
||||||
@ -28,7 +32,7 @@ const externalCtx = await context(
|
|||||||
const zenumlCtx = await context(
|
const zenumlCtx = await context(
|
||||||
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid-zenuml' })
|
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 () => {
|
const rebuildAll = async () => {
|
||||||
console.time('Rebuild time');
|
console.time('Rebuild time');
|
||||||
@ -75,10 +79,11 @@ function sendEventsToAll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createServer() {
|
async function createServer() {
|
||||||
|
await generateLangium();
|
||||||
handleFileChange();
|
handleFileChange();
|
||||||
const app = express();
|
const app = express();
|
||||||
chokidar
|
chokidar
|
||||||
.watch('**/src/**/*.{js,ts,yaml,json}', {
|
.watch('**/src/**/*.{js,ts,langium,yaml,json}', {
|
||||||
ignoreInitial: true,
|
ignoreInitial: true,
|
||||||
ignored: [/node_modules/, /dist/, /docs/, /coverage/],
|
ignored: [/node_modules/, /dist/, /docs/, /coverage/],
|
||||||
})
|
})
|
||||||
@ -87,12 +92,16 @@ async function createServer() {
|
|||||||
if (!['add', 'change'].includes(event)) {
|
if (!['add', 'change'].includes(event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (/\.langium$/.test(path)) {
|
||||||
|
await generateLangium();
|
||||||
|
}
|
||||||
console.log(`${path} changed. Rebuilding...`);
|
console.log(`${path} changed. Rebuilding...`);
|
||||||
handleFileChange();
|
handleFileChange();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.get('/events', eventsHandler);
|
app.get('/events', eventsHandler);
|
||||||
|
app.use(express.static('./packages/parser/dist'));
|
||||||
app.use(express.static('./packages/mermaid/dist'));
|
app.use(express.static('./packages/mermaid/dist'));
|
||||||
app.use(express.static('./packages/mermaid-zenuml/dist'));
|
app.use(express.static('./packages/mermaid-zenuml/dist'));
|
||||||
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
||||||
|
@ -6,3 +6,6 @@ cypress/plugins/index.js
|
|||||||
coverage
|
coverage
|
||||||
*.json
|
*.json
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
# autogenereated by langium-cli
|
||||||
|
generated/
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -47,3 +47,6 @@ stats/
|
|||||||
demos/dev/**
|
demos/dev/**
|
||||||
!/demos/dev/example.html
|
!/demos/dev/example.html
|
||||||
!/demos/dev/reload.js
|
!/demos/dev/reload.js
|
||||||
|
|
||||||
|
# autogenereated by langium-cli
|
||||||
|
generated/
|
||||||
|
@ -10,3 +10,6 @@ stats
|
|||||||
.nyc_output
|
.nyc_output
|
||||||
# Autogenerated by `pnpm run --filter mermaid types:build-config`
|
# Autogenerated by `pnpm run --filter mermaid types:build-config`
|
||||||
packages/mermaid/src/config.type.ts
|
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 type { TemplateType } from 'rollup-plugin-visualizer/dist/plugin/template-types.js';
|
||||||
import istanbul from 'vite-plugin-istanbul';
|
import istanbul from 'vite-plugin-istanbul';
|
||||||
import { packageOptions } from '../.build/common.js';
|
import { packageOptions } from '../.build/common.js';
|
||||||
|
import { generateLangium } from '../.build/generateLangium.js';
|
||||||
|
|
||||||
const visualize = process.argv.includes('--visualize');
|
const visualize = process.argv.includes('--visualize');
|
||||||
const watch = process.argv.includes('--watch');
|
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
|
// @ts-expect-error According to the type definitions, rollup plugins are incompatible with vite
|
||||||
typescript({ compilerOptions: { declaration: false } }),
|
typescript({ compilerOptions: { declaration: false } }),
|
||||||
istanbul({
|
istanbul({
|
||||||
exclude: ['node_modules', 'test/', '__mocks__'],
|
exclude: ['node_modules', 'test/', '__mocks__', 'generated'],
|
||||||
extension: ['.js', '.ts'],
|
extension: ['.js', '.ts'],
|
||||||
requireEnv: true,
|
requireEnv: true,
|
||||||
forceBuildInstrument: coverage,
|
forceBuildInstrument: coverage,
|
||||||
@ -106,18 +107,24 @@ const buildPackage = async (entryName: keyof typeof packageOptions) => {
|
|||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
|
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 buildPackage(pkg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
await generateLangium();
|
||||||
|
|
||||||
if (watch) {
|
if (watch) {
|
||||||
|
await build(getBuildConfig({ minify: false, watch, core: false, entryName: 'parser' }));
|
||||||
build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' }));
|
build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' }));
|
||||||
if (!mermaidOnly) {
|
if (!mermaidOnly) {
|
||||||
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
|
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
|
||||||
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-zenuml' }));
|
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-zenuml' }));
|
||||||
}
|
}
|
||||||
} else if (visualize) {
|
} 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: true, entryName: 'mermaid' }));
|
||||||
await build(getBuildConfig({ minify: false, core: false, entryName: 'mermaid' }));
|
await build(getBuildConfig({ minify: false, core: false, entryName: 'mermaid' }));
|
||||||
} else {
|
} else {
|
||||||
|
@ -14,6 +14,7 @@ async function createServer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
app.use(express.static('./packages/parser/dist'));
|
||||||
app.use(express.static('./packages/mermaid/dist'));
|
app.use(express.static('./packages/mermaid/dist'));
|
||||||
app.use(express.static('./packages/mermaid-zenuml/dist'));
|
app.use(express.static('./packages/mermaid-zenuml/dist'));
|
||||||
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
||||||
|
@ -71,6 +71,7 @@
|
|||||||
"knut",
|
"knut",
|
||||||
"knutsveidqvist",
|
"knutsveidqvist",
|
||||||
"laganeckas",
|
"laganeckas",
|
||||||
|
"langium",
|
||||||
"linetype",
|
"linetype",
|
||||||
"lintstagedrc",
|
"lintstagedrc",
|
||||||
"logmsg",
|
"logmsg",
|
||||||
|
@ -15,11 +15,11 @@
|
|||||||
"git graph"
|
"git graph"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"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:esbuild": "pnpm run -r clean && ts-node-esm --transpileOnly .esbuild/build.ts",
|
||||||
"build:mermaid": "pnpm build:esbuild --mermaid",
|
"build:mermaid": "pnpm build:esbuild --mermaid",
|
||||||
"build:viz": "pnpm build:esbuild --visualize",
|
"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": "ts-node-esm --transpileOnly .esbuild/server.ts",
|
||||||
"dev:vite": "ts-node-esm --transpileOnly .vite/server.ts",
|
"dev:vite": "ts-node-esm --transpileOnly .vite/server.ts",
|
||||||
"dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev:vite",
|
"dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev:vite",
|
||||||
@ -107,6 +107,7 @@
|
|||||||
"jison": "^0.4.18",
|
"jison": "^0.4.18",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsdom": "^22.0.0",
|
"jsdom": "^22.0.0",
|
||||||
|
"langium-cli": "2.0.1",
|
||||||
"lint-staged": "^13.2.1",
|
"lint-staged": "^13.2.1",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
* This is a dummy parser that satisfies the mermaid API logic.
|
* This is a dummy parser that satisfies the mermaid API logic.
|
||||||
*/
|
*/
|
||||||
export default {
|
export default {
|
||||||
parser: { yy: {} },
|
|
||||||
parse: () => {
|
parse: () => {
|
||||||
// no op
|
// no op
|
||||||
},
|
},
|
||||||
|
@ -72,6 +72,7 @@
|
|||||||
"khroma": "^2.0.0",
|
"khroma": "^2.0.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mdast-util-from-markdown": "^1.3.0",
|
"mdast-util-from-markdown": "^1.3.0",
|
||||||
|
"mermaid-parser": "workspace:^",
|
||||||
"stylis": "^4.1.3",
|
"stylis": "^4.1.3",
|
||||||
"ts-dedent": "^2.2.0",
|
"ts-dedent": "^2.2.0",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
|
@ -50,7 +50,10 @@ export class Diagram {
|
|||||||
this.parser.parse = (text: string) =>
|
this.parser.parse = (text: string) =>
|
||||||
originalParse(cleanupComments(extractFrontMatter(text, this.db, configApi.addDirective)));
|
originalParse(cleanupComments(extractFrontMatter(text, this.db, configApi.addDirective)));
|
||||||
|
|
||||||
this.parser.parser.yy = this.db;
|
if (this.parser.parser !== undefined) {
|
||||||
|
// 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.init = diagram.init;
|
||||||
this.parse();
|
this.parse();
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,6 @@ export const addDiagrams = () => {
|
|||||||
styles: {}, // should never be used
|
styles: {}, // should never be used
|
||||||
renderer: {}, // should never be used
|
renderer: {}, // should never be used
|
||||||
parser: {
|
parser: {
|
||||||
parser: { yy: {} },
|
|
||||||
parse: () => {
|
parse: () => {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Diagrams beginning with --- are not valid. ' +
|
'Diagrams beginning with --- are not valid. ' +
|
||||||
|
@ -39,7 +39,6 @@ describe('DiagramAPI', () => {
|
|||||||
parse: (_text) => {
|
parse: (_text) => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
parser: { yy: {} },
|
|
||||||
},
|
},
|
||||||
renderer: {},
|
renderer: {},
|
||||||
styles: {},
|
styles: {},
|
||||||
|
@ -80,7 +80,7 @@ export type DrawDefinition = (
|
|||||||
|
|
||||||
export interface ParserDefinition {
|
export interface ParserDefinition {
|
||||||
parse: (text: string) => void;
|
parse: (text: string) => void;
|
||||||
parser: { yy: DiagramDB };
|
parser?: { yy: DiagramDB };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,9 +30,6 @@ describe('diagram detection', () => {
|
|||||||
parse: () => {
|
parse: () => {
|
||||||
// no-op
|
// no-op
|
||||||
},
|
},
|
||||||
parser: {
|
|
||||||
yy: {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
renderer: {},
|
renderer: {},
|
||||||
styles: {},
|
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 @@ const diagram: DiagramDefinition = {
|
|||||||
db: {},
|
db: {},
|
||||||
renderer,
|
renderer,
|
||||||
parser: {
|
parser: {
|
||||||
parser: { yy: {} },
|
|
||||||
parse: (): void => {
|
parse: (): void => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
@ -1,24 +1,31 @@
|
|||||||
// @ts-ignore - jison doesn't export types
|
import { parser } from './infoParser.js';
|
||||||
import { parser } from './parser/info.jison';
|
|
||||||
import { db } from './infoDb.js';
|
|
||||||
|
|
||||||
describe('info diagram', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
parser.yy = db;
|
|
||||||
parser.yy.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
describe('info', () => {
|
||||||
it('should handle an info definition', () => {
|
it('should handle an info definition', () => {
|
||||||
const str = `info`;
|
const str = `info`;
|
||||||
parser.parse(str);
|
expect(() => {
|
||||||
|
parser.parse(str);
|
||||||
expect(db.getInfo()).toBeFalsy();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle an info definition with showInfo', () => {
|
it('should handle an info definition with showInfo', () => {
|
||||||
const str = `info 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 type { InfoFields, InfoDB } from './infoTypes.js';
|
||||||
|
import { version } from '../../../package.json';
|
||||||
|
|
||||||
export const DEFAULT_INFO_DB: InfoFields = {
|
export const DEFAULT_INFO_DB: InfoFields = { version } as const;
|
||||||
info: false,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
let info: boolean = DEFAULT_INFO_DB.info;
|
export const getVersion = (): string => DEFAULT_INFO_DB.version;
|
||||||
|
|
||||||
export const setInfo = (toggle: boolean): void => {
|
|
||||||
info = toggle;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getInfo = (): boolean => info;
|
|
||||||
|
|
||||||
const clear = (): void => {
|
|
||||||
info = DEFAULT_INFO_DB.info;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const db: InfoDB = {
|
export const db: InfoDB = {
|
||||||
clear,
|
getVersion,
|
||||||
setInfo,
|
|
||||||
getInfo,
|
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||||
// @ts-ignore - jison doesn't export types
|
import { parser } from './infoParser.js';
|
||||||
import parser from './parser/info.jison';
|
|
||||||
import { db } from './infoDb.js';
|
import { db } from './infoDb.js';
|
||||||
import { renderer } from './infoRenderer.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';
|
import type { DiagramDB } from '../../diagram-api/types.js';
|
||||||
|
|
||||||
export interface InfoFields {
|
export interface InfoFields {
|
||||||
info: boolean;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InfoDB extends DiagramDB {
|
export interface InfoDB extends DiagramDB {
|
||||||
clear: () => void;
|
getVersion: () => string;
|
||||||
setInfo: (info: boolean) => void;
|
|
||||||
getInfo: () => boolean;
|
|
||||||
}
|
}
|
||||||
|
@ -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) => {
|
parse: (_text) => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
parser: { yy: {} },
|
|
||||||
},
|
},
|
||||||
styles: () => {
|
styles: () => {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
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>
|
||||||
|
|
||||||
|
[![NPM](https://img.shields.io/npm/v/mermaid-parser)](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"]
|
||||||
|
}
|
6560
pnpm-lock.yaml
generated
6560
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
import jison from './.vite/jisonPlugin.js';
|
import jison from './.vite/jisonPlugin.js';
|
||||||
import jsonSchemaPlugin from './.vite/jsonSchemaPlugin.js';
|
import jsonSchemaPlugin from './.vite/jsonSchemaPlugin.js';
|
||||||
import typescript from '@rollup/plugin-typescript';
|
import typescript from '@rollup/plugin-typescript';
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defaultExclude, defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
resolve: {
|
resolve: {
|
||||||
@ -22,7 +22,7 @@ export default defineConfig({
|
|||||||
provider: 'v8',
|
provider: 'v8',
|
||||||
reporter: ['text', 'json', 'html', 'lcov'],
|
reporter: ['text', 'json', 'html', 'lcov'],
|
||||||
reportsDirectory: './coverage/vitest',
|
reportsDirectory: './coverage/vitest',
|
||||||
exclude: ['**/node_modules/**', '**/tests/**', '**/__mocks__/**'],
|
exclude: [...defaultExclude, './tests/**', '**/__mocks__/**', '**/generated/'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user