From 276fd7ad842ad54b992b463d6dbeef585944d412 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 25 Aug 2023 12:54:44 +0530 Subject: [PATCH 1/7] refactor: Move directive processing before parsing Directives and fronmatter will be preprocessed and removed from the text before parsing. --- .../interfaces/mermaidAPI.ParseOptions.md | 2 +- .../interfaces/mermaidAPI.RenderResult.md | 4 +- docs/config/setup/modules/mermaidAPI.md | 24 +-- packages/mermaid/src/Diagram.ts | 34 ++-- packages/mermaid/src/__mocks__/mermaidAPI.ts | 1 - packages/mermaid/src/config.ts | 2 +- packages/mermaid/src/diagram-api/comments.ts | 2 +- .../mermaid/src/diagram-api/diagramAPI.ts | 25 ++- .../src/diagram-api/frontmatter.spec.ts | 155 +++++++++++++----- .../mermaid/src/diagram-api/frontmatter.ts | 57 ++++--- packages/mermaid/src/diagram-api/types.ts | 10 +- packages/mermaid/src/directiveUtils.ts | 82 --------- packages/mermaid/src/mermaidAPI.ts | 40 ++--- packages/mermaid/src/preprocess.ts | 58 +++++++ packages/mermaid/src/utils.spec.ts | 38 ++++- packages/mermaid/src/utils.ts | 91 +--------- .../mermaid/src/utils/sanitizeDirective.ts | 84 ++++++++++ 17 files changed, 382 insertions(+), 327 deletions(-) delete mode 100644 packages/mermaid/src/directiveUtils.ts create mode 100644 packages/mermaid/src/preprocess.ts create mode 100644 packages/mermaid/src/utils/sanitizeDirective.ts diff --git a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md index 2082a081ea..56f9146419 100644 --- a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md +++ b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md @@ -16,4 +16,4 @@ #### Defined in -[mermaidAPI.ts:78](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L78) +[mermaidAPI.ts:76](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L76) diff --git a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md index f84a51b87b..2c1504285d 100644 --- a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md +++ b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md @@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present. #### Defined in -[mermaidAPI.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L98) +[mermaidAPI.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L96) --- @@ -51,4 +51,4 @@ The svg code for the rendered graph. #### Defined in -[mermaidAPI.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L88) +[mermaidAPI.ts:86](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L86) diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index d5d4a1cbc1..3c2b5bb84a 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -25,13 +25,13 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi) #### Defined in -[mermaidAPI.ts:82](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L82) +[mermaidAPI.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L80) ## Variables ### mermaidAPI -• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `parseDirective`: (`p`: `any`, `statement`: `string`, `context`: `string`, `type`: `string`) => `void` ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }> +• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`, `metadata`: { `title?`: `string` }) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }> ## mermaidAPI configuration defaults @@ -96,7 +96,7 @@ mermaid.initialize(config); #### Defined in -[mermaidAPI.ts:673](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L673) +[mermaidAPI.ts:662](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L662) ## Functions @@ -127,7 +127,7 @@ Return the last node appended #### Defined in -[mermaidAPI.ts:310](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L310) +[mermaidAPI.ts:318](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L318) --- @@ -153,7 +153,7 @@ the cleaned up svgCode #### Defined in -[mermaidAPI.ts:256](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L256) +[mermaidAPI.ts:264](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L264) --- @@ -179,7 +179,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L185) +[mermaidAPI.ts:193](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L193) --- @@ -202,7 +202,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:233](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L233) +[mermaidAPI.ts:241](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L241) --- @@ -229,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L169) +[mermaidAPI.ts:177](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L177) --- @@ -249,7 +249,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L155) +[mermaidAPI.ts:163](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L163) --- @@ -269,7 +269,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L126) +[mermaidAPI.ts:134](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L134) --- @@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code #### Defined in -[mermaidAPI.ts:287](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L287) +[mermaidAPI.ts:295](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L295) --- @@ -320,4 +320,4 @@ Remove any existing elements from the given document #### Defined in -[mermaidAPI.ts:360](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L360) +[mermaidAPI.ts:368](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L368) diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts index 308e141d03..9d665c71c5 100644 --- a/packages/mermaid/src/Diagram.ts +++ b/packages/mermaid/src/Diagram.ts @@ -2,9 +2,7 @@ import * as configApi from './config.js'; import { log } from './logger.js'; import { getDiagram, registerDiagram } from './diagram-api/diagramAPI.js'; import { detectType, getDiagramLoader } from './diagram-api/detectType.js'; -import { extractFrontMatter } from './diagram-api/frontmatter.js'; import { UnknownDiagramError } from './errors.js'; -import { cleanupComments } from './diagram-api/comments.js'; import type { DetailedError } from './utils.js'; import type { DiagramDefinition } from './diagram-api/types.js'; @@ -22,7 +20,7 @@ export class Diagram { private init?: DiagramDefinition['init']; private detectError?: UnknownDiagramError; - constructor(public text: string) { + constructor(public text: string, public metadata: { title?: string } = {}) { this.text += '\n'; const cnf = configApi.getConfig(); try { @@ -37,19 +35,6 @@ export class Diagram { this.db = diagram.db; this.renderer = diagram.renderer; this.parser = diagram.parser; - const originalParse = this.parser.parse.bind(this.parser); - // Wrap the jison parse() method to handle extracting frontmatter. - // - // This can't be done in this.parse() because some code - // directly calls diagram.parser.parse(), bypassing this.parse(). - // - // Similarly, we can't do this in getDiagramFromText() because some code - // calls diagram.db.clear(), which would reset anything set by - // extractFrontMatter(). - - this.parser.parse = (text: string) => - originalParse(cleanupComments(extractFrontMatter(text, this.db, configApi.addDirective))); - this.parser.parser.yy = this.db; this.init = diagram.init; this.parse(); @@ -60,7 +45,15 @@ export class Diagram { throw this.detectError; } this.db.clear?.(); - this.init?.(configApi.getConfig()); + const config = configApi.getConfig(); + this.init?.(config); + // These 2 blocks were added for legacy compatibility. Do not add more such blocks. Use frontmatter instead. + if (this.metadata.title) { + this.db.setDiagramTitle?.(this.metadata.title); + } + if (config.wrap) { + this.db.setWrap?.(config.wrap); + } this.parser.parse(this.text); } @@ -86,7 +79,10 @@ export class Diagram { * @throws {@link UnknownDiagramError} if the diagram type can not be found. * @privateRemarks This is exported as part of the public mermaidAPI. */ -export const getDiagramFromText = async (text: string): Promise => { +export const getDiagramFromText = async ( + text: string, + metadata: { title?: string } = {} +): Promise => { const type = detectType(text, configApi.getConfig()); try { // Trying to find the diagram @@ -101,5 +97,5 @@ export const getDiagramFromText = async (text: string): Promise => { const { id, diagram } = await loader(); registerDiagram(id, diagram); } - return new Diagram(text); + return new Diagram(text, metadata); }; diff --git a/packages/mermaid/src/__mocks__/mermaidAPI.ts b/packages/mermaid/src/__mocks__/mermaidAPI.ts index a2d19ef246..de4cb61dfc 100644 --- a/packages/mermaid/src/__mocks__/mermaidAPI.ts +++ b/packages/mermaid/src/__mocks__/mermaidAPI.ts @@ -13,7 +13,6 @@ export const mermaidAPI = { svg: '', }), parse: mAPI.parse, - parseDirective: vi.fn(), initialize: vi.fn(), getConfig: configApi.getConfig, setConfig: configApi.setConfig, diff --git a/packages/mermaid/src/config.ts b/packages/mermaid/src/config.ts index eb24b6268f..ede3a568df 100644 --- a/packages/mermaid/src/config.ts +++ b/packages/mermaid/src/config.ts @@ -3,7 +3,7 @@ import { log } from './logger.js'; import theme from './themes/index.js'; import config from './defaultConfig.js'; import type { MermaidConfig } from './config.type.js'; -import { sanitizeDirective } from './utils.js'; +import { sanitizeDirective } from './utils/sanitizeDirective.js'; export const defaultConfig: MermaidConfig = Object.freeze(config); diff --git a/packages/mermaid/src/diagram-api/comments.ts b/packages/mermaid/src/diagram-api/comments.ts index be39b0a0f7..8141deee0f 100644 --- a/packages/mermaid/src/diagram-api/comments.ts +++ b/packages/mermaid/src/diagram-api/comments.ts @@ -4,5 +4,5 @@ * @returns cleaned text */ export const cleanupComments = (text: string): string => { - return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, ''); + return text.replace(/^\s*%%(?!{)[^\n]+\n?/gm, '').trimStart(); }; diff --git a/packages/mermaid/src/diagram-api/diagramAPI.ts b/packages/mermaid/src/diagram-api/diagramAPI.ts index 00da66ffe4..0cb20b3b2e 100644 --- a/packages/mermaid/src/diagram-api/diagramAPI.ts +++ b/packages/mermaid/src/diagram-api/diagramAPI.ts @@ -6,7 +6,6 @@ import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox.js import { addStylesForDiagram } from '../styles.js'; import type { DiagramDefinition, DiagramDetector } from './types.js'; import * as _commonDb from '../commonDb.js'; -import { parseDirective as _parseDirective } from '../directiveUtils.js'; /* Packaging and exposing resources for external diagrams so that they can import @@ -21,8 +20,6 @@ export const setupGraphViewbox = _setupGraphViewbox; export const getCommonDb = () => { return _commonDb; }; -export const parseDirective = (p: any, statement: string, context: string, type: string) => - _parseDirective(p, statement, context, type); const diagrams: Record = {}; export interface Detectors { @@ -52,17 +49,17 @@ export const registerDiagram = ( } addStylesForDiagram(id, diagram.styles); - if (diagram.injectUtils) { - diagram.injectUtils( - log, - setLogLevel, - getConfig, - sanitizeText, - setupGraphViewbox, - getCommonDb(), - parseDirective - ); - } + diagram.injectUtils?.( + log, + setLogLevel, + getConfig, + sanitizeText, + setupGraphViewbox, + getCommonDb(), + () => { + // parseDirective is removed. This is a no-op for legacy support. + } + ); }; export const getDiagram = (name: string): DiagramDefinition => { diff --git a/packages/mermaid/src/diagram-api/frontmatter.spec.ts b/packages/mermaid/src/diagram-api/frontmatter.spec.ts index 03d46c300c..90ef97cb6e 100644 --- a/packages/mermaid/src/diagram-api/frontmatter.spec.ts +++ b/packages/mermaid/src/diagram-api/frontmatter.spec.ts @@ -1,84 +1,139 @@ -import { vi } from 'vitest'; import { extractFrontMatter } from './frontmatter.js'; -const dbMock = () => ({ setDiagramTitle: vi.fn() }); -const setConfigMock = vi.fn(); - describe('extractFrontmatter', () => { - beforeEach(() => { - setConfigMock.mockClear(); - }); - it('returns text unchanged if no frontmatter', () => { - expect(extractFrontMatter('diagram', dbMock())).toEqual('diagram'); + expect(extractFrontMatter('diagram')).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "diagram", + } + `); }); it('returns text unchanged if frontmatter lacks closing delimiter', () => { const text = `---\ntitle: foo\ndiagram`; - expect(extractFrontMatter(text, dbMock())).toEqual(text); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "--- + title: foo + diagram", + } + `); }); it('handles empty frontmatter', () => { - const db = dbMock(); const text = `---\n\n---\ndiagram`; - expect(extractFrontMatter(text, db)).toEqual('diagram'); - expect(db.setDiagramTitle).not.toHaveBeenCalled(); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "diagram", + } + `); }); it('handles frontmatter without mappings', () => { - const db = dbMock(); - const text = `---\n1\n---\ndiagram`; - expect(extractFrontMatter(text, db)).toEqual('diagram'); - expect(db.setDiagramTitle).not.toHaveBeenCalled(); + expect(extractFrontMatter(`---\n1\n---\ndiagram`)).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "diagram", + } + `); + expect(extractFrontMatter(`---\n-1\n-2\n---\ndiagram`)).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "diagram", + } + `); + expect(extractFrontMatter(`---\nnull\n---\ndiagram`)).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "diagram", + } + `); }); it('does not try to parse frontmatter at the end', () => { - const db = dbMock(); const text = `diagram\n---\ntitle: foo\n---\n`; - expect(extractFrontMatter(text, db)).toEqual(text); - expect(db.setDiagramTitle).not.toHaveBeenCalled(); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": {}, + "text": "diagram + --- + title: foo + --- + ", + } + `); }); it('handles frontmatter with multiple delimiters', () => { - const db = dbMock(); const text = `---\ntitle: foo---bar\n---\ndiagram\n---\ntest`; - expect(extractFrontMatter(text, db)).toEqual('diagram\n---\ntest'); - expect(db.setDiagramTitle).toHaveBeenCalledWith('foo---bar'); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": { + "title": "foo---bar", + }, + "text": "diagram + --- + test", + } + `); }); it('handles frontmatter with multi-line string and multiple delimiters', () => { - const db = dbMock(); const text = `---\ntitle: |\n multi-line string\n ---\n---\ndiagram`; - expect(extractFrontMatter(text, db)).toEqual('diagram'); - expect(db.setDiagramTitle).toHaveBeenCalledWith('multi-line string\n---\n'); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": { + "title": "multi-line string + --- + ", + }, + "text": "diagram", + } + `); }); it('handles frontmatter with title', () => { - const db = dbMock(); const text = `---\ntitle: foo\n---\ndiagram`; - expect(extractFrontMatter(text, db)).toEqual('diagram'); - expect(db.setDiagramTitle).toHaveBeenCalledWith('foo'); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": { + "title": "foo", + }, + "text": "diagram", + } + `); }); it('handles booleans in frontmatter properly', () => { - const db = dbMock(); const text = `---\ntitle: true\n---\ndiagram`; - expect(extractFrontMatter(text, db)).toEqual('diagram'); - expect(db.setDiagramTitle).toHaveBeenCalledWith('true'); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": { + "title": "true", + }, + "text": "diagram", + } + `); }); it('ignores unspecified frontmatter keys', () => { - const db = dbMock(); const text = `---\ninvalid: true\ntitle: foo\ntest: bar\n---\ndiagram`; - expect(extractFrontMatter(text, db)).toEqual('diagram'); - expect(db.setDiagramTitle).toHaveBeenCalledWith('foo'); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": { + "title": "foo", + }, + "text": "diagram", + } + `); }); it('throws exception for invalid YAML syntax', () => { const text = `---\n!!!\n---\ndiagram`; - expect(() => extractFrontMatter(text, dbMock())).toThrow( - 'tag suffix cannot contain exclamation marks' - ); + expect(() => extractFrontMatter(text)).toThrow('tag suffix cannot contain exclamation marks'); }); it('handles frontmatter with config', () => { @@ -92,9 +147,25 @@ config: array: [1, 2, 3] --- diagram`; - expect(extractFrontMatter(text, {}, setConfigMock)).toEqual('diagram'); - expect(setConfigMock).toHaveBeenCalledWith({ - graph: { string: 'hello', number: 14, boolean: false, array: [1, 2, 3] }, - }); + expect(extractFrontMatter(text)).toMatchInlineSnapshot(` + { + "metadata": { + "config": { + "graph": { + "array": [ + 1, + 2, + 3, + ], + "boolean": false, + "number": 14, + "string": "hello", + }, + }, + "title": "hello", + }, + "text": "diagram", + } + `); }); }); diff --git a/packages/mermaid/src/diagram-api/frontmatter.ts b/packages/mermaid/src/diagram-api/frontmatter.ts index 0fd2917eae..c95e05f2cf 100644 --- a/packages/mermaid/src/diagram-api/frontmatter.ts +++ b/packages/mermaid/src/diagram-api/frontmatter.ts @@ -1,6 +1,5 @@ import type { MermaidConfig } from '../config.type.js'; import { frontMatterRegex } from './regexes.js'; -import type { DiagramDB } from './types.js'; // The "* as yaml" part is necessary for tree-shaking import * as yaml from 'js-yaml'; @@ -11,43 +10,51 @@ interface FrontMatterMetadata { config?: MermaidConfig; } +export interface FrontMatterResult { + text: string; + metadata: FrontMatterMetadata; +} + /** * Extract and parse frontmatter from text, if present, and sets appropriate * properties in the provided db. * @param text - The text that may have a YAML frontmatter. - * @param db - Diagram database, could be of any diagram. - * @param setDiagramConfig - Optional function to set diagram config. * @returns text with frontmatter stripped out */ -export function extractFrontMatter( - text: string, - db: DiagramDB, - setDiagramConfig?: (config: MermaidConfig) => void -): string { +export function extractFrontMatter(text: string): FrontMatterResult { const matches = text.match(frontMatterRegex); if (!matches) { - return text; + return { + text, + metadata: {}, + }; } - const parsed: FrontMatterMetadata = yaml.load(matches[1], { - // To support config, we need JSON schema. - // https://www.yaml.org/spec/1.2/spec.html#id2803231 - schema: yaml.JSON_SCHEMA, - }) as FrontMatterMetadata; + let parsed: FrontMatterMetadata = + yaml.load(matches[1], { + // To support config, we need JSON schema. + // https://www.yaml.org/spec/1.2/spec.html#id2803231 + schema: yaml.JSON_SCHEMA, + }) ?? {}; - if (parsed?.title) { - // toString() is necessary because YAML could parse the title as a number/boolean - db.setDiagramTitle?.(parsed.title.toString()); - } + // To handle runtime data type changes + parsed = typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}; - if (parsed?.displayMode) { - // toString() is necessary because YAML could parse the title as a number/boolean - db.setDisplayMode?.(parsed.displayMode.toString()); - } + const metadata: FrontMatterMetadata = {}; - if (parsed?.config) { - setDiagramConfig?.(parsed.config); + // Only add properties that are explicitly supported, if they exist + if (parsed.displayMode) { + metadata.displayMode = parsed.displayMode.toString(); + } + if (parsed.title) { + metadata.title = parsed.title.toString(); + } + if (parsed.config) { + metadata.config = parsed.config; } - return text.slice(matches[0].length); + return { + text: text.slice(matches[0].length), + metadata, + }; } diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts index 2ac7fba127..e9def2421f 100644 --- a/packages/mermaid/src/diagram-api/types.ts +++ b/packages/mermaid/src/diagram-api/types.ts @@ -29,6 +29,7 @@ export interface DiagramDB { getAccDescription?: () => string; setDisplayMode?: (title: string) => void; + setWrap?: (wrap: boolean) => void; bindFunctions?: (element: Element) => void; } @@ -83,15 +84,6 @@ export interface ParserDefinition { parser: { yy: DiagramDB }; } -/** - * Type for function parse directive from diagram code. - * - * @param statement - - * @param context - - * @param type - - */ -export type ParseDirectiveDefinition = (statement: string, context: string, type: string) => void; - export type HTML = d3.Selection; export type SVG = d3.Selection; diff --git a/packages/mermaid/src/directiveUtils.ts b/packages/mermaid/src/directiveUtils.ts deleted file mode 100644 index baf628e74c..0000000000 --- a/packages/mermaid/src/directiveUtils.ts +++ /dev/null @@ -1,82 +0,0 @@ -import * as configApi from './config.js'; -import { log } from './logger.js'; - -let currentDirective: { type?: string; args?: any } | undefined = {}; - -export const parseDirective = function ( - p: any, - statement: string, - context: string, - type: string -): void { - log.debug('parseDirective is being called', statement, context, type); - try { - if (statement !== undefined) { - statement = statement.trim(); - switch (context) { - case 'open_directive': - currentDirective = {}; - break; - case 'type_directive': - if (!currentDirective) { - throw new Error('currentDirective is undefined'); - } - currentDirective.type = statement.toLowerCase(); - break; - case 'arg_directive': - if (!currentDirective) { - throw new Error('currentDirective is undefined'); - } - currentDirective.args = JSON.parse(statement); - break; - case 'close_directive': - handleDirective(p, currentDirective, type); - currentDirective = undefined; - break; - } - } - } catch (error) { - log.error( - `Error while rendering sequenceDiagram directive: ${statement} jison context: ${context}` - ); - // @ts-ignore: TODO Fix ts errors - log.error(error.message); - } -}; - -const handleDirective = function (p: any, directive: any, type: string): void { - log.info(`Directive type=${directive.type} with args:`, directive.args); - switch (directive.type) { - case 'init': - case 'initialize': { - ['config'].forEach((prop) => { - if (directive.args[prop] !== undefined) { - if (type === 'flowchart-v2') { - type = 'flowchart'; - } - directive.args[type] = directive.args[prop]; - delete directive.args[prop]; - } - }); - configApi.addDirective(directive.args); - break; - } - case 'wrap': - case 'nowrap': - if (p && p['setWrap']) { - p.setWrap(directive.type === 'wrap'); - } - break; - case 'themeCss': - log.warn('themeCss encountered'); - break; - default: - log.warn( - `Unhandled directive: source: '%%{${directive.type}: ${JSON.stringify( - directive.args ? directive.args : {} - )}}%%`, - directive - ); - break; - } -}; diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index bb75700348..f71fe27a76 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -23,14 +23,12 @@ import { attachFunctions } from './interactionDb.js'; import { log, setLogLevel } from './logger.js'; import getStyles from './styles.js'; import theme from './themes/index.js'; -import utils from './utils.js'; import DOMPurify from 'dompurify'; import type { MermaidConfig } from './config.type.js'; import { evaluate } from './diagrams/common/common.js'; import isEmpty from 'lodash-es/isEmpty.js'; import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js'; -import { parseDirective } from './directiveUtils.js'; -import { extractFrontMatter } from './diagram-api/frontmatter.js'; +import { preprocessDiagram } from './preprocess.js'; // diagram names that support classDef statements const CLASSDEF_DIAGRAMS = [ @@ -98,6 +96,13 @@ export interface RenderResult { bindFunctions?: (element: Element) => void; } +function processAndSetConfigs(text: string) { + const processed = preprocessDiagram(text); + configApi.reset(); + configApi.addDirective(processed.config); + return processed; +} + /** * Parse the text and validate the syntax. * @param text - The mermaid diagram definition. @@ -108,6 +113,9 @@ export interface RenderResult { async function parse(text: string, parseOptions?: ParseOptions): Promise { addDiagrams(); + + text = processAndSetConfigs(text).code; + try { await getDiagramFromText(text); } catch (error) { @@ -384,18 +392,8 @@ const render = async function ( ): Promise { addDiagrams(); - configApi.reset(); - - // We need to add the directives before creating the diagram. - // So extractFrontMatter is called twice. Once here and once in the diagram parser. - // This can be fixed in a future refactor. - extractFrontMatter(text, {}, configApi.addDirective); - - // Add Directives. - const graphInit = utils.detectInit(text); - if (graphInit) { - configApi.addDirective(graphInit); - } + const processed = processAndSetConfigs(text); + text = processed.code; const config = configApi.getConfig(); log.debug(config); @@ -405,15 +403,6 @@ const render = async function ( text = MAX_TEXTLENGTH_EXCEEDED_MSG; } - // clean up text CRLFs - text = text.replace(/\r\n?/g, '\n'); // parser problems on CRLF ignore all CR and leave LF;; - - // clean up html tags so that all attributes use single quotes, parser throws error on double quotes - text = text.replace( - /<(\w+)([^>]*)>/g, - (match, tag, attributes) => '<' + tag + attributes.replace(/="([^"]*)"/g, "='$1'") + '>' - ); - const idSelector = '#' + id; const iFrameID = 'i' + id; const iFrameID_selector = '#' + iFrameID; @@ -476,7 +465,7 @@ const render = async function ( let parseEncounteredException; try { - diag = await getDiagramFromText(text); + diag = await getDiagramFromText(text, { title: processed.title }); } catch (error) { diag = new Diagram('error'); parseEncounteredException = error; @@ -673,7 +662,6 @@ function addA11yInfo( export const mermaidAPI = Object.freeze({ render, parse, - parseDirective, getDiagramFromText, initialize, getConfig: configApi.getConfig, diff --git a/packages/mermaid/src/preprocess.ts b/packages/mermaid/src/preprocess.ts new file mode 100644 index 0000000000..3c33ce30f8 --- /dev/null +++ b/packages/mermaid/src/preprocess.ts @@ -0,0 +1,58 @@ +import { cleanupComments } from './diagram-api/comments.js'; +import { extractFrontMatter } from './diagram-api/frontmatter.js'; +import utils, { cleanAndMerge, removeDirectives } from './utils.js'; + +const cleanupText = (code: string) => { + return ( + code + // parser problems on CRLF ignore all CR and leave LF;; + .replace(/\r\n?/g, '\n') + // clean up html tags so that all attributes use single quotes, parser throws error on double quotes + .replace( + /<(\w+)([^>]*)>/g, + (match, tag, attributes) => '<' + tag + attributes.replace(/="([^"]*)"/g, "='$1'") + '>' + ) + ); +}; + +const processFrontmatter = (code: string) => { + const { text, metadata } = extractFrontMatter(code); + const { displayMode, title, config = {} } = metadata; + if (displayMode) { + // Needs to be supported for legacy reasons + if (!config.gantt) { + config.gantt = {}; + } + config.gantt.displayMode = displayMode; + } + return { title, config, text }; +}; + +const processDirectives = (code: string) => { + const initDirective = utils.detectInit(code) ?? {}; + const wrapDirectives = utils.detectDirective(code, 'wrap'); + if (Array.isArray(wrapDirectives)) { + initDirective.wrap = wrapDirectives.some(({ type }) => { + type === 'wrap'; + }); + } else if (wrapDirectives?.type === 'wrap') { + initDirective.wrap = true; + } + return { + text: removeDirectives(code), + directive: initDirective, + }; +}; + +export const preprocessDiagram = (code: string) => { + const cleanedCode = cleanupText(code); + const frontMatterResult = processFrontmatter(cleanedCode); + const directiveResult = processDirectives(frontMatterResult.text); + const config = cleanAndMerge(frontMatterResult.config, directiveResult.directive); + code = cleanupComments(directiveResult.text); + return { + code, + title: frontMatterResult.title, + config, + }; +}; diff --git a/packages/mermaid/src/utils.spec.ts b/packages/mermaid/src/utils.spec.ts index 271dc588c6..e1398efc73 100644 --- a/packages/mermaid/src/utils.spec.ts +++ b/packages/mermaid/src/utils.spec.ts @@ -1,10 +1,11 @@ import { vi } from 'vitest'; -import utils, { cleanAndMerge } from './utils.js'; +import utils, { cleanAndMerge, detectDirective } from './utils.js'; import assignWithDepth from './assignWithDepth.js'; import { detectType } from './diagram-api/detectType.js'; import { addDiagrams } from './diagram-api/diagram-orchestration.js'; import memoize from 'lodash-es/memoize.js'; import { MockedD3 } from './tests/MockedD3.js'; +import { preprocessDiagram } from './preprocess.js'; addDiagrams(); @@ -158,13 +159,38 @@ describe('when detecting chart type ', function () { const type = detectType(str); expect(type).toBe('flowchart'); }); + it('should handle a wrap directive', () => { + const wrap = { type: 'wrap', args: null }; + expect(detectDirective('%%{wrap}%%', 'wrap')).toEqual(wrap); + expect( + detectDirective( + `%%{ + wrap + }%%`, + 'wrap' + ) + ).toEqual(wrap); + expect( + detectDirective( + `%%{ + + wrap + + }%%`, + 'wrap' + ) + ).toEqual(wrap); + expect(detectDirective('%%{wrap:}%%', 'wrap')).toEqual(wrap); + expect(detectDirective('%%{wrap: }%%', 'wrap')).toEqual(wrap); + expect(detectDirective('graph', 'wrap')).not.toEqual(wrap); + }); it('should handle an initialize definition', function () { const str = ` %%{initialize: { 'logLevel': 0, 'theme': 'dark' }}%% sequenceDiagram Alice->Bob: hi`; const type = detectType(str); - const init = utils.detectInit(str); + const init = preprocessDiagram(str).config; expect(type).toBe('sequence'); expect(init).toEqual({ logLevel: 0, theme: 'dark' }); }); @@ -174,7 +200,7 @@ Alice->Bob: hi`; sequenceDiagram Alice->Bob: hi`; const type = detectType(str); - const init = utils.detectInit(str); + const init = preprocessDiagram(str).config; expect(type).toBe('sequence'); expect(init).toEqual({ logLevel: 0, theme: 'dark' }); }); @@ -184,7 +210,7 @@ Alice->Bob: hi`; sequenceDiagram Alice->Bob: hi`; const type = detectType(str); - const init = utils.detectInit(str); + const init = preprocessDiagram(str).config; expect(type).toBe('sequence'); expect(init).toEqual({ logLevel: 0, theme: 'dark', sequence: { wrap: true } }); }); @@ -199,7 +225,7 @@ Alice->Bob: hi`; sequenceDiagram Alice->Bob: hi`; const type = detectType(str); - const init = utils.detectInit(str); + const init = preprocessDiagram(str).config; expect(type).toBe('sequence'); expect(init).toEqual({ logLevel: 0, theme: 'dark' }); }); @@ -214,7 +240,7 @@ Alice->Bob: hi`; sequenceDiagram Alice->Bob: hi`; const type = detectType(str); - const init = utils.detectInit(str); + const init = preprocessDiagram(str).config; expect(type).toBe('sequence'); expect(init).toEqual({ logLevel: 0, theme: 'dark' }); }); diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts index 42b4ee67e8..70de197dad 100644 --- a/packages/mermaid/src/utils.ts +++ b/packages/mermaid/src/utils.ts @@ -25,7 +25,7 @@ import { select, } from 'd3'; import common from './diagrams/common/common.js'; -import { configKeys } from './defaultConfig.js'; +import { sanitizeDirective } from './utils/sanitizeDirective.js'; import { log } from './logger.js'; import { detectType } from './diagram-api/detectType.js'; import assignWithDepth from './assignWithDepth.js'; @@ -62,7 +62,6 @@ const d3CurveTypes = { const directiveWithoutOpen = /\s*(?:(\w+)(?=:):|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; - /** * Detects the init config object from the text * @@ -197,6 +196,10 @@ export const detectDirective = function ( } }; +export const removeDirectives = function (text: string): string { + return text.replace(directiveRegex, ''); +}; + /** * Detects whether a substring in present in a given array * @@ -842,88 +845,6 @@ export const entityDecode = function (html: string): string { return unescape(decoder.textContent); }; -/** - * Sanitizes directive objects - * - * @param args - Directive's JSON - */ -export const sanitizeDirective = (args: unknown): void => { - log.debug('sanitizeDirective called with', args); - - // Return if not an object - if (typeof args !== 'object' || args == null) { - return; - } - - // Sanitize each element if an array - if (Array.isArray(args)) { - args.forEach((arg) => sanitizeDirective(arg)); - return; - } - - // Sanitize each key if an object - for (const key of Object.keys(args)) { - log.debug('Checking key', key); - if ( - key.startsWith('__') || - key.includes('proto') || - key.includes('constr') || - !configKeys.has(key) || - args[key] == null - ) { - log.debug('sanitize deleting key: ', key); - delete args[key]; - continue; - } - - // Recurse if an object - if (typeof args[key] === 'object') { - log.debug('sanitizing object', key); - sanitizeDirective(args[key]); - continue; - } - - const cssMatchers = ['themeCSS', 'fontFamily', 'altFontFamily']; - for (const cssKey of cssMatchers) { - if (key.includes(cssKey)) { - log.debug('sanitizing css option', key); - args[key] = sanitizeCss(args[key]); - } - } - } - - if (args.themeVariables) { - for (const k of Object.keys(args.themeVariables)) { - const val = args.themeVariables[k]; - if (val?.match && !val.match(/^[\d "#%(),.;A-Za-z]+$/)) { - args.themeVariables[k] = ''; - } - } - } - log.debug('After sanitization', args); -}; - -export const sanitizeCss = (str: string): string => { - let startCnt = 0; - let endCnt = 0; - - for (const element of str) { - if (startCnt < endCnt) { - return '{ /* ERROR: Unbalanced CSS */ }'; - } - if (element === '{') { - startCnt++; - } else if (element === '}') { - endCnt++; - } - } - if (startCnt !== endCnt) { - return '{ /* ERROR: Unbalanced CSS */ }'; - } - // Todo add more checks here - return str; -}; - export interface DetailedError { str: string; hash: any; @@ -1021,8 +942,6 @@ export default { runFunc, entityDecode, initIdGenerator, - sanitizeDirective, - sanitizeCss, insertTitle, parseFontSize, }; diff --git a/packages/mermaid/src/utils/sanitizeDirective.ts b/packages/mermaid/src/utils/sanitizeDirective.ts new file mode 100644 index 0000000000..9b7e7da5c0 --- /dev/null +++ b/packages/mermaid/src/utils/sanitizeDirective.ts @@ -0,0 +1,84 @@ +import { configKeys } from '../defaultConfig.js'; +import { log } from '../logger.js'; + +/** + * Sanitizes directive objects + * + * @param args - Directive's JSON + */ +export const sanitizeDirective = (args: any): void => { + log.debug('sanitizeDirective called with', args); + + // Return if not an object + if (typeof args !== 'object' || args == null) { + return; + } + + // Sanitize each element if an array + if (Array.isArray(args)) { + args.forEach((arg) => sanitizeDirective(arg)); + return; + } + + // Sanitize each key if an object + for (const key of Object.keys(args)) { + log.debug('Checking key', key); + if ( + key.startsWith('__') || + key.includes('proto') || + key.includes('constr') || + !configKeys.has(key) || + args[key] == null + ) { + log.debug('sanitize deleting key: ', key); + delete args[key]; + continue; + } + + // Recurse if an object + if (typeof args[key] === 'object') { + log.debug('sanitizing object', key); + sanitizeDirective(args[key]); + continue; + } + + const cssMatchers = ['themeCSS', 'fontFamily', 'altFontFamily']; + for (const cssKey of cssMatchers) { + if (key.includes(cssKey)) { + log.debug('sanitizing css option', key); + args[key] = sanitizeCss(args[key]); + } + } + } + + if (args.themeVariables) { + for (const k of Object.keys(args.themeVariables)) { + const val = args.themeVariables[k]; + if (val?.match && !val.match(/^[\d "#%(),.;A-Za-z]+$/)) { + args.themeVariables[k] = ''; + } + } + } + log.debug('After sanitization', args); +}; + +export const sanitizeCss = (str: string): string => { + let startCnt = 0; + let endCnt = 0; + + for (const element of str) { + if (startCnt < endCnt) { + return '{ /* ERROR: Unbalanced CSS */ }'; + } + if (element === '{') { + startCnt++; + } else if (element === '}') { + endCnt++; + } + } + if (startCnt !== endCnt) { + return '{ /* ERROR: Unbalanced CSS */ }'; + } + // Todo add more checks here + return str; +}; From f0883be0e3e481afbb9d21abf8f81c420b99227b Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 25 Aug 2023 12:55:35 +0530 Subject: [PATCH 2/7] refactor: Update DBs to remove directive handling --- packages/mermaid/src/diagrams/c4/c4Db.js | 6 ---- .../mermaid/src/diagrams/class/classDb.ts | 7 ---- packages/mermaid/src/diagrams/er/erDb.js | 6 ---- .../mermaid/src/diagrams/flowchart/flowDb.js | 6 ---- .../mermaid/src/diagrams/gantt/ganttDb.js | 6 ---- .../mermaid/src/diagrams/git/gitGraphAst.js | 6 ---- packages/mermaid/src/diagrams/pie/pie.spec.ts | 11 ------- packages/mermaid/src/diagrams/pie/pieDb.ts | 7 ---- packages/mermaid/src/diagrams/pie/pieTypes.ts | 3 +- .../parser/quadrant.jison.spec.ts | 32 +------------------ .../src/diagrams/quadrant-chart/quadrantDb.ts | 8 ----- .../src/diagrams/requirement/requirementDb.js | 6 ---- .../src/diagrams/sequence/sequenceDb.js | 6 ---- .../diagrams/sequence/sequenceDiagram.spec.js | 6 ---- .../mermaid/src/diagrams/state/stateDb.js | 6 ---- .../diagrams/state/stateDiagram-v2.spec.js | 10 ------ .../src/diagrams/state/stateDiagram.spec.js | 10 ------ .../src/diagrams/timeline/timeline.spec.js | 20 +----------- .../src/diagrams/timeline/timelineDb.js | 6 ---- .../src/diagrams/user-journey/journeyDb.js | 6 ---- 20 files changed, 3 insertions(+), 171 deletions(-) diff --git a/packages/mermaid/src/diagrams/c4/c4Db.js b/packages/mermaid/src/diagrams/c4/c4Db.js index 09dcc13cda..2dc014d9ed 100644 --- a/packages/mermaid/src/diagrams/c4/c4Db.js +++ b/packages/mermaid/src/diagrams/c4/c4Db.js @@ -1,4 +1,3 @@ -import mermaidAPI from '../../mermaidAPI.js'; import * as configApi from '../../config.js'; import { sanitizeText } from '../common/common.js'; import { setAccTitle, getAccTitle, getAccDescription, setAccDescription } from '../../commonDb.js'; @@ -33,10 +32,6 @@ export const setC4Type = function (c4TypeParam) { c4Type = sanitizedText; }; -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - //type, from, to, label, ?techn, ?descr, ?sprite, ?tags, $link export const addRel = function (type, from, to, label, techn, descr, sprite, tags, link) { // Don't allow label nulling @@ -816,7 +811,6 @@ export default { getAccTitle, getAccDescription, setAccDescription, - parseDirective, getConfig: () => configApi.getConfig().c4, clear, LINETYPE, diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index b14b1d07a1..11e80ffc88 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -5,7 +5,6 @@ import { log } from '../../logger.js'; import * as configApi from '../../config.js'; import common from '../common/common.js'; import utils from '../../utils.js'; -import mermaidAPI from '../../mermaidAPI.js'; import { setAccTitle, getAccTitle, @@ -37,11 +36,6 @@ let functions: any[] = []; const sanitizeText = (txt: string) => common.sanitizeText(txt, configApi.getConfig()); -export const parseDirective = function (statement: string, context: string, type: string) { - // @ts-ignore Don't wanna mess it up - mermaidAPI.parseDirective(this, statement, context, type); -}; - const splitClassNameAndType = function (id: string) { let genericType = ''; let className = id; @@ -456,7 +450,6 @@ export const addClassesToNamespace = function (id: string, classNames: string[]) }; export default { - parseDirective, setAccTitle, getAccTitle, getAccDescription, diff --git a/packages/mermaid/src/diagrams/er/erDb.js b/packages/mermaid/src/diagrams/er/erDb.js index 2f5116cbfe..559337ba90 100644 --- a/packages/mermaid/src/diagrams/er/erDb.js +++ b/packages/mermaid/src/diagrams/er/erDb.js @@ -1,5 +1,4 @@ import { log } from '../../logger.js'; -import mermaidAPI from '../../mermaidAPI.js'; import * as configApi from '../../config.js'; import { @@ -28,10 +27,6 @@ const Identification = { IDENTIFYING: 'IDENTIFYING', }; -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - const addEntity = function (name) { if (entities[name] === undefined) { entities[name] = { attributes: [] }; @@ -85,7 +80,6 @@ const clear = function () { export default { Cardinality, Identification, - parseDirective, getConfig: () => configApi.getConfig().er, addEntity, addAttributes, diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.js b/packages/mermaid/src/diagrams/flowchart/flowDb.js index ea8fa71d2b..6e161a33a7 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDb.js +++ b/packages/mermaid/src/diagrams/flowchart/flowDb.js @@ -2,7 +2,6 @@ import { select } from 'd3'; import utils from '../../utils.js'; import * as configApi from '../../config.js'; import common from '../common/common.js'; -import mermaidAPI from '../../mermaidAPI.js'; import { log } from '../../logger.js'; import { setAccTitle, @@ -34,10 +33,6 @@ let funs = []; const sanitizeText = (txt) => common.sanitizeText(txt, config); -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - /** * Function to lookup domId from id in the graph definition. * @@ -771,7 +766,6 @@ export const lex = { firstGraph, }; export default { - parseDirective, defaultConfig: () => configApi.defaultConfig.flowchart, setAccTitle, getAccTitle, diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.js b/packages/mermaid/src/diagrams/gantt/ganttDb.js index da838f8392..20e1ad70a0 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttDb.js +++ b/packages/mermaid/src/diagrams/gantt/ganttDb.js @@ -6,7 +6,6 @@ import dayjsAdvancedFormat from 'dayjs/plugin/advancedFormat.js'; import { log } from '../../logger.js'; import * as configApi from '../../config.js'; import utils from '../../utils.js'; -import mermaidAPI from '../../mermaidAPI.js'; import { setAccTitle, @@ -42,10 +41,6 @@ let weekday = 'sunday'; // The serial order of the task in the script let lastOrder = 0; -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - export const clear = function () { sections = []; tasks = []; @@ -730,7 +725,6 @@ export const bindFunctions = function (element) { }; export default { - parseDirective, getConfig: () => configApi.getConfig().gantt, clear, setDateFormat, diff --git a/packages/mermaid/src/diagrams/git/gitGraphAst.js b/packages/mermaid/src/diagrams/git/gitGraphAst.js index 416479d151..5b34c6b656 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphAst.js +++ b/packages/mermaid/src/diagrams/git/gitGraphAst.js @@ -1,6 +1,5 @@ import { log } from '../../logger.js'; import { random } from '../../utils.js'; -import mermaidAPI from '../../mermaidAPI.js'; import * as configApi from '../../config.js'; import { getConfig } from '../../config.js'; import common from '../common/common.js'; @@ -33,10 +32,6 @@ function getId() { return random({ length: 7 }); } -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - // /** // * @param currentCommit // * @param otherCommit @@ -507,7 +502,6 @@ export const commitType = { }; export default { - parseDirective, getConfig: () => configApi.getConfig().gitGraph, setDirection, setOptions, diff --git a/packages/mermaid/src/diagrams/pie/pie.spec.ts b/packages/mermaid/src/diagrams/pie/pie.spec.ts index 7c8e0809a3..564e12f0f1 100644 --- a/packages/mermaid/src/diagrams/pie/pie.spec.ts +++ b/packages/mermaid/src/diagrams/pie/pie.spec.ts @@ -62,17 +62,6 @@ describe('pie', () => { expect(sections['bat']).toBe(40); }); - it('should handle simple pie with a directive', () => { - parser.parse(`%%{init: {'logLevel':0}}%% - pie - "ash" : 60 - "bat" : 40 - `); - const sections = db.getSections(); - expect(sections['ash']).toBe(60); - expect(sections['bat']).toBe(40); - }); - it('should handle simple pie with a title', () => { parser.parse(`pie title a 60/40 pie "ash" : 60 diff --git a/packages/mermaid/src/diagrams/pie/pieDb.ts b/packages/mermaid/src/diagrams/pie/pieDb.ts index dbe50f08a0..2a0dc16f31 100644 --- a/packages/mermaid/src/diagrams/pie/pieDb.ts +++ b/packages/mermaid/src/diagrams/pie/pieDb.ts @@ -1,5 +1,4 @@ import { log } from '../../logger.js'; -import { parseDirective as _parseDirective } from '../../directiveUtils.js'; import { getConfig as commonGetConfig } from '../../config.js'; import { sanitizeText } from '../common/common.js'; import { @@ -11,7 +10,6 @@ import { setAccDescription, clear as commonClear, } from '../../commonDb.js'; -import type { ParseDirectiveDefinition } from '../../diagram-api/types.js'; import type { PieFields, PieDB, Sections } from './pieTypes.js'; import type { RequiredDeep } from 'type-fest'; import type { PieDiagramConfig } from '../../config.type.js'; @@ -31,10 +29,6 @@ const config: Required = structuredClone(DEFAULT_PIE_CONFIG); const getConfig = (): Required => structuredClone(config); -const parseDirective: ParseDirectiveDefinition = (statement, context, type) => { - _parseDirective(this, statement, context, type); -}; - const clear = (): void => { sections = structuredClone(DEFAULT_PIE_DB.sections); showData = DEFAULT_PIE_DB.showData; @@ -67,7 +61,6 @@ const getShowData = (): boolean => showData; export const db: PieDB = { getConfig, - parseDirective, clear, setDiagramTitle, getDiagramTitle, diff --git a/packages/mermaid/src/diagrams/pie/pieTypes.ts b/packages/mermaid/src/diagrams/pie/pieTypes.ts index 67fb1dca25..6ba3ab92e5 100644 --- a/packages/mermaid/src/diagrams/pie/pieTypes.ts +++ b/packages/mermaid/src/diagrams/pie/pieTypes.ts @@ -1,5 +1,5 @@ import type { PieDiagramConfig } from '../../config.type.js'; -import type { DiagramDB, ParseDirectiveDefinition } from '../../diagram-api/types.js'; +import type { DiagramDB } from '../../diagram-api/types.js'; export interface PieFields { sections: Sections; @@ -46,7 +46,6 @@ export interface PieDB extends DiagramDB { getConfig: () => Required; // common db - parseDirective: ParseDirectiveDefinition; clear: () => void; setDiagramTitle: (title: string) => void; getDiagramTitle: () => string; diff --git a/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison.spec.ts b/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison.spec.ts index faa9281f0d..d10cb21340 100644 --- a/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison.spec.ts +++ b/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison.spec.ts @@ -19,7 +19,6 @@ const mockDB: Record> = { setYAxisTopText: vi.fn(), setYAxisBottomText: vi.fn(), setDiagramTitle: vi.fn(), - parseDirective: vi.fn(), addPoint: vi.fn(), }; @@ -45,23 +44,6 @@ describe('Testing quadrantChart jison file', () => { expect(parserFnConstructor(str)).not.toThrow(); }); - it('should be able to parse directive', () => { - const str = - '%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%% \n quadrantChart'; - expect(parserFnConstructor(str)).not.toThrow(); - expect(mockDB.parseDirective.mock.calls[0]).toEqual(['%%{', 'open_directive']); - expect(mockDB.parseDirective.mock.calls[1]).toEqual(['init', 'type_directive']); - expect(mockDB.parseDirective.mock.calls[2]).toEqual([ - '{"quadrantChart": {"chartWidth": 600, "chartHeight": 600} }', - 'arg_directive', - ]); - expect(mockDB.parseDirective.mock.calls[3]).toEqual([ - '}%%', - 'close_directive', - 'quadrantChart', - ]); - }); - it('should be able to parse xAxis text', () => { let str = 'quadrantChart\nx-axis urgent --> not urgent'; expect(parserFnConstructor(str)).not.toThrow(); @@ -243,8 +225,7 @@ describe('Testing quadrantChart jison file', () => { }); it('should be able to parse the whole chart', () => { - const str = `%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%% - quadrantChart + const str = `quadrantChart title Analytics and Business Intelligence Platforms x-axis "Completeness of Vision ❤" --> "x-axis-2" y-axis Ability to Execute --> "y-axis-2" @@ -258,17 +239,6 @@ describe('Testing quadrantChart jison file', () => { Incorta: [0.20, 0.30]`; expect(parserFnConstructor(str)).not.toThrow(); - expect(mockDB.parseDirective.mock.calls[0]).toEqual(['%%{', 'open_directive']); - expect(mockDB.parseDirective.mock.calls[1]).toEqual(['init', 'type_directive']); - expect(mockDB.parseDirective.mock.calls[2]).toEqual([ - '{"quadrantChart": {"chartWidth": 600, "chartHeight": 600} }', - 'arg_directive', - ]); - expect(mockDB.parseDirective.mock.calls[3]).toEqual([ - '}%%', - 'close_directive', - 'quadrantChart', - ]); expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'Completeness of Vision ❤', type: 'text', diff --git a/packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts b/packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts index c0c0f4c8ac..4466f9fa4b 100644 --- a/packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts +++ b/packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts @@ -1,5 +1,3 @@ -import { log } from '../../logger.js'; -import mermaidAPI from '../../mermaidAPI.js'; import * as configApi from '../../config.js'; import { sanitizeText } from '../common/common.js'; import { @@ -94,11 +92,6 @@ function getQuadrantData() { return quadrantBuilder.build(); } -export const parseDirective = function (statement: string, context: string, type: string) { - // @ts-ignore: TODO Fix ts errors - mermaidAPI.parseDirective(this, statement, context, type); -}; - const clear = function () { quadrantBuilder.clear(); commonClear(); @@ -117,7 +110,6 @@ export default { setYAxisBottomText, addPoint, getQuadrantData, - parseDirective, clear, setAccTitle, getAccTitle, diff --git a/packages/mermaid/src/diagrams/requirement/requirementDb.js b/packages/mermaid/src/diagrams/requirement/requirementDb.js index 1d0a3e2e12..03629aeac8 100644 --- a/packages/mermaid/src/diagrams/requirement/requirementDb.js +++ b/packages/mermaid/src/diagrams/requirement/requirementDb.js @@ -1,6 +1,5 @@ import * as configApi from '../../config.js'; import { log } from '../../logger.js'; -import mermaidAPI from '../../mermaidAPI.js'; import { setAccTitle, @@ -48,10 +47,6 @@ const Relationships = { TRACES: 'traces', }; -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - const addRequirement = (name, type) => { if (requirements[name] === undefined) { requirements[name] = { @@ -149,7 +144,6 @@ export default { VerifyType, Relationships, - parseDirective, getConfig: () => configApi.getConfig().req, addRequirement, diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.js b/packages/mermaid/src/diagrams/sequence/sequenceDb.js index b5d68fb9f5..ff8a7c0ae8 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.js @@ -1,4 +1,3 @@ -import mermaidAPI from '../../mermaidAPI.js'; import * as configApi from '../../config.js'; import { log } from '../../logger.js'; import { sanitizeText } from '../common/common.js'; @@ -25,10 +24,6 @@ let currentBox = undefined; let lastCreated = undefined; let lastDestroyed = undefined; -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - export const addBox = function (data) { boxes.push({ name: data.text, @@ -619,7 +614,6 @@ export default { getBoxes, getDiagramTitle, setDiagramTitle, - parseDirective, getConfig: () => configApi.getConfig().sequence, clear, parseMessage, diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 0b84fbe356..5e0943699c 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -2051,9 +2051,7 @@ describe('when rendering a sequenceDiagram with directives', () => { it('should handle one actor, when theme is dark and logLevel is 1 DX1 (dfg1)', async () => { const str = ` -%%{init: { "theme": "dark", "logLevel": 1 } }%% sequenceDiagram -%%{wrap}%% participant Alice `; diagram = new Diagram(str); @@ -2062,8 +2060,6 @@ participant Alice const { bounds, models } = diagram.renderer.bounds.getBounds(); const mermaid = mermaidAPI.getConfig(); - expect(mermaid.theme).toBe('dark'); - expect(mermaid.logLevel).toBe(1); expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0); expect(bounds.starty).toBe(0); @@ -2073,7 +2069,6 @@ participant Alice }); it('should handle one actor, when logLevel is 3 (dfg0)', async () => { const str = ` -%%{initialize: { "logLevel": 3 }}%% sequenceDiagram participant Alice `; @@ -2083,7 +2078,6 @@ participant Alice const { bounds, models } = diagram.renderer.bounds.getBounds(); const mermaid = mermaidAPI.getConfig(); - expect(mermaid.logLevel).toBe(3); expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0); expect(bounds.starty).toBe(0); diff --git a/packages/mermaid/src/diagrams/state/stateDb.js b/packages/mermaid/src/diagrams/state/stateDb.js index d9c789a99b..56a0a4cc6d 100644 --- a/packages/mermaid/src/diagrams/state/stateDb.js +++ b/packages/mermaid/src/diagrams/state/stateDb.js @@ -1,6 +1,5 @@ import { log } from '../../logger.js'; import { generateId } from '../../utils.js'; -import mermaidAPI from '../../mermaidAPI.js'; import common from '../common/common.js'; import * as configApi from '../../config.js'; import { @@ -77,10 +76,6 @@ export const relationType = { const clone = (o) => JSON.parse(JSON.stringify(o)); -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - const setRootDoc = (o) => { log.info('Setting root doc', o); // rootDoc = { id: 'root', doc: o }; @@ -547,7 +542,6 @@ const setDirection = (dir) => { const trimColon = (str) => (str && str[0] === ':' ? str.substr(1).trim() : str.trim()); export default { - parseDirective, getConfig: () => configApi.getConfig().state, addState, clear, diff --git a/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js b/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js index e64ecfdf89..c387ff7b3d 100644 --- a/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js +++ b/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js @@ -55,16 +55,6 @@ describe('state diagram V2, ', function () { const title = stateDb.getAccTitle(); expect(title).toBe('a simple title of the diagram'); }); - it('simple with directive', function () { - const str = `%%{init: {'logLevel': 0 }}%% - stateDiagram-v2\n - State1 : this is another string - [*] --> State1 - State1 --> [*] - `; - - parser.parse(str); - }); it('should handle relation definitions', function () { const str = `stateDiagram-v2\n [*] --> State1 diff --git a/packages/mermaid/src/diagrams/state/stateDiagram.spec.js b/packages/mermaid/src/diagrams/state/stateDiagram.spec.js index e6e470140c..536031c815 100644 --- a/packages/mermaid/src/diagrams/state/stateDiagram.spec.js +++ b/packages/mermaid/src/diagrams/state/stateDiagram.spec.js @@ -66,16 +66,6 @@ describe('state diagram, ', function () { const title = stateDb.getAccTitle(); expect(title).toBe('a simple title of the diagram'); }); - it('simple with directive', function () { - const str = `%%{init: {'logLevel': 0 }}%% - stateDiagram\n - State1 : this is another string - [*] --> State1 - State1 --> [*] - `; - - parser.parse(str); - }); it('should handle relation definitions', function () { const str = `stateDiagram\n [*] --> State1 diff --git a/packages/mermaid/src/diagrams/timeline/timeline.spec.js b/packages/mermaid/src/diagrams/timeline/timeline.spec.js index 1f6a960244..a66a7f2c12 100644 --- a/packages/mermaid/src/diagrams/timeline/timeline.spec.js +++ b/packages/mermaid/src/diagrams/timeline/timeline.spec.js @@ -1,26 +1,8 @@ import { parser as timeline } from './parser/timeline.jison'; import * as timelineDB from './timelineDb.js'; -// import { injectUtils } from './mermaidUtils.js'; import * as _commonDb from '../../commonDb.js'; -import { parseDirective as _parseDirective } from '../../directiveUtils.js'; -import { - log, - setLogLevel, - getConfig, - sanitizeText, - setupGraphViewBox, -} from '../../diagram-api/diagramAPI.js'; - -// injectUtils( -// log, -// setLogLevel, -// getConfig, -// sanitizeText, -// setupGraphViewBox, -// _commonDb, -// _parseDirective -// ); +import { setLogLevel } from '../../diagram-api/diagramAPI.js'; describe('when parsing a timeline ', function () { beforeEach(function () { diff --git a/packages/mermaid/src/diagrams/timeline/timelineDb.js b/packages/mermaid/src/diagrams/timeline/timelineDb.js index 337cfe4416..91cee51759 100644 --- a/packages/mermaid/src/diagrams/timeline/timelineDb.js +++ b/packages/mermaid/src/diagrams/timeline/timelineDb.js @@ -1,4 +1,3 @@ -import { parseDirective as _parseDirective } from '../../directiveUtils.js'; import * as commonDb from '../../commonDb.js'; let currentSection = ''; let currentTaskId = 0; @@ -9,10 +8,6 @@ const rawTasks = []; export const getCommonDb = () => commonDb; -export const parseDirective = (statement, context, type) => { - _parseDirective(this, statement, context, type); -}; - export const clear = function () { sections.length = 0; tasks.length = 0; @@ -104,5 +99,4 @@ export default { addTask, addTaskOrg, addEvent, - parseDirective, }; diff --git a/packages/mermaid/src/diagrams/user-journey/journeyDb.js b/packages/mermaid/src/diagrams/user-journey/journeyDb.js index d4f34e9422..5812acffe1 100644 --- a/packages/mermaid/src/diagrams/user-journey/journeyDb.js +++ b/packages/mermaid/src/diagrams/user-journey/journeyDb.js @@ -1,4 +1,3 @@ -import mermaidAPI from '../../mermaidAPI.js'; import * as configApi from '../../config.js'; import { setAccTitle, @@ -16,10 +15,6 @@ const sections = []; const tasks = []; const rawTasks = []; -export const parseDirective = function (statement, context, type) { - mermaidAPI.parseDirective(this, statement, context, type); -}; - export const clear = function () { sections.length = 0; tasks.length = 0; @@ -118,7 +113,6 @@ const getActors = function () { }; export default { - parseDirective, getConfig: () => configApi.getConfig().journey, clear, setDiagramTitle, From 1e0918c2ffbb213fb60bcd010f922d7ebae9553c Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 25 Aug 2023 12:55:58 +0530 Subject: [PATCH 3/7] refactor: Remove directives from grammar --- .../src/diagrams/c4/parser/c4Diagram.jison | 30 -- .../diagrams/class/parser/classDiagram.jison | 30 -- .../src/diagrams/er/parser/erDiagram.jison | 31 +- .../src/diagrams/flowchart/parser/flow.jison | 36 +-- .../src/diagrams/gantt/parser/gantt.jison | 32 +- .../src/diagrams/git/gitGraphParser.spec | 288 ------------------ .../src/diagrams/git/parser/gitGraph.jison | 31 -- .../mermaid/src/diagrams/pie/parser/pie.jison | 32 -- .../quadrant-chart/parser/quadrant.jison | 32 -- .../parser/requirementDiagram.jison | 24 +- .../sequence/parser/sequenceDiagram.jison | 32 +- .../diagrams/state/parser/stateDiagram.jison | 35 +-- .../diagrams/timeline/parser/timeline.jison | 33 -- .../user-journey/parser/journey.jison | 32 -- 14 files changed, 8 insertions(+), 690 deletions(-) delete mode 100644 packages/mermaid/src/diagrams/git/gitGraphParser.spec diff --git a/packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison b/packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison index 1dfa69ef1f..a6bd8a6ec2 100644 --- a/packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison +++ b/packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison @@ -72,25 +72,16 @@ %x string_kv_key %x string_kv_value -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %x acc_title %x acc_descr %x acc_descr_multiline %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } .*direction\s+TB[^\n]* return 'direction_tb'; .*direction\s+BT[^\n]* return 'direction_bt'; .*direction\s+RL[^\n]* return 'direction_rl'; .*direction\s+LR[^\n]* return 'direction_lr'; -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; "title"\s[^#\n;]+ return 'title'; @@ -207,7 +198,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} start : mermaidDoc | direction - | directive start ; direction @@ -225,26 +215,6 @@ mermaidDoc : graphConfig ; -directive - : openDirective typeDirective closeDirective NEWLINE - | openDirective typeDirective ':' argDirective closeDirective NEWLINE - ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'c4Context'); } - ; graphConfig : C4_CONTEXT NEWLINE statements EOF {yy.setC4Type($1)} diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index 8fdfced75b..6066fdc352 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -13,9 +13,6 @@ %x href %x callback_name %x callback_args -%x open_directive -%x type_directive -%x arg_directive %x acc_title %x acc_descr %x acc_descr_multiline @@ -24,15 +21,10 @@ %x namespace %x namespace-body %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } .*direction\s+TB[^\n]* return 'direction_tb'; .*direction\s+BT[^\n]* return 'direction_bt'; .*direction\s+RL[^\n]* return 'direction_rl'; .*direction\s+LR[^\n]* return 'direction_lr'; -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; \%\%(?!\{)*[^\n]*(\r?\n?)+ /* skip comments */ \%\%[^\n]*(\r?\n)* /* skip comments */ accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } @@ -220,7 +212,6 @@ line was introduced with 'click'. start : mermaidDoc - | directive start | statements ; @@ -239,27 +230,6 @@ mermaidDoc : graphConfig ; -directive - : openDirective typeDirective closeDirective NEWLINE - | openDirective typeDirective ':' argDirective closeDirective NEWLINE - ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'class'); } - ; - graphConfig : CLASS_DIAGRAM NEWLINE statements EOF ; diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison index 0a2549268d..4338983ec1 100644 --- a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison +++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison @@ -1,7 +1,7 @@ %lex %options case-insensitive -%x open_directive type_directive arg_directive block +%x block %x acc_title %x acc_descr %x acc_descr_multiline @@ -14,11 +14,6 @@ accDescr\s*":"\s* { this.begin("ac accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} [\}] { this.popState(); } [^\}]* return "acc_descr_multiline_value"; -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; [\n]+ return 'NEWLINE'; \s+ /* skip whitespace */ [\s]+ return 'SPACE'; @@ -75,7 +70,6 @@ o\{ return 'ZERO_OR_MORE'; start : 'ER_DIAGRAM' document 'EOF' { /*console.log('finished parsing');*/ } - | directive start ; document @@ -90,14 +84,9 @@ line | EOF { $$=[];} ; -directive - : openDirective typeDirective closeDirective 'NEWLINE' - | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' - ; statement - : directive - | entityName relSpec entityName ':' role + : entityName relSpec entityName ':' role { yy.addEntity($1); yy.addEntity($3); @@ -185,20 +174,4 @@ role | 'ALPHANUM' { $$ = $1; } ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'er'); } - ; - %% diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison index 8d746f8082..6dad36d25f 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison @@ -23,17 +23,8 @@ %x href %x callbackname %x callbackargs -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } (?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } @@ -272,35 +263,10 @@ that id. %% /* language grammar */ start - : mermaidDoc - | directive start - ; - -directive - : openDirective typeDirective closeDirective separator - | openDirective typeDirective ':' argDirective closeDirective separator - ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($type_directive, 'type_directive'); } - ; - -argDirective - : arg_directive { $arg_directive = $arg_directive.trim().replace(/'/g, '"'); yy.parseDirective($arg_directive, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'flowchart'); } - ; - -mermaidDoc : graphConfig document ; + document : /* empty */ { $$ = [];} diff --git a/packages/mermaid/src/diagrams/gantt/parser/gantt.jison b/packages/mermaid/src/diagrams/gantt/parser/gantt.jison index f7fd40c1bc..b4daab5dca 100644 --- a/packages/mermaid/src/diagrams/gantt/parser/gantt.jison +++ b/packages/mermaid/src/diagrams/gantt/parser/gantt.jison @@ -11,19 +11,11 @@ %x href %x callbackname %x callbackargs -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %x acc_title %x acc_descr %x acc_descr_multiline %% \%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } (?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } @@ -112,8 +104,7 @@ weekday\s+sunday return 'weekday_sunday' %% /* language grammar */ start - : directive start - | gantt document 'EOF' { return $2; } + : gantt document 'EOF' { return $2; } ; document @@ -155,13 +146,8 @@ statement | section { yy.addSection($1.substr(8));$$=$1.substr(8); } | clickStatement | taskTxt taskData {yy.addTask($1,$2);$$='task';} - | directive ; -directive - : openDirective typeDirective closeDirective 'NL' - | openDirective typeDirective ':' argDirective closeDirective 'NL' - ; /* click allows any combination of href and call. @@ -192,20 +178,4 @@ clickStatementDebug | click href {$$=$1 + ' ' + $2;} ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'gantt'); } - ; - %% diff --git a/packages/mermaid/src/diagrams/git/gitGraphParser.spec b/packages/mermaid/src/diagrams/git/gitGraphParser.spec deleted file mode 100644 index 0a8256c5b7..0000000000 --- a/packages/mermaid/src/diagrams/git/gitGraphParser.spec +++ /dev/null @@ -1,288 +0,0 @@ - -// Todo reintroduce without cryptoRandomString -import gitGraphAst from './gitGraphAst'; -import { parser } from './parser/gitGraph'; -import randomString from 'crypto-random-string'; -import cryptoRandomString from 'crypto-random-string'; - -jest.mock('crypto-random-string'); - -describe('when parsing a gitGraph', function() { - let randomNumber; - beforeEach(function() { - parser.yy = gitGraphAst; - parser.yy.clear(); - randomNumber = 0; - cryptoRandomString.mockImplementation(() => { - randomNumber = randomNumber + 1; - return String(randomNumber); - }); - }); - afterEach(function() { - cryptoRandomString.mockReset(); - }); - it('should handle a gitGraph definition', function() { - const str = 'gitGraph:\n' + 'commit\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(Object.keys(commits).length).toBe(1); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getDirection()).toBe('LR'); - expect(Object.keys(parser.yy.getBranches()).length).toBe(1); - }); - - it('should handle a gitGraph definition with empty options', function() { - const str = 'gitGraph:\n' + 'options\n' + 'end\n' + 'commit\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(parser.yy.getOptions()).toEqual({}); - expect(Object.keys(commits).length).toBe(1); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getDirection()).toBe('LR'); - expect(Object.keys(parser.yy.getBranches()).length).toBe(1); - }); - - it('should handle a gitGraph definition with valid options', function() { - const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"}\n' + 'end\n' + 'commit\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - expect(parser.yy.getOptions()['key']).toBe('value'); - expect(Object.keys(commits).length).toBe(1); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getDirection()).toBe('LR'); - expect(Object.keys(parser.yy.getBranches()).length).toBe(1); - }); - - it('should not fail on a gitGraph with malformed json', function() { - const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"\n' + 'end\n' + 'commit\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(1); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getDirection()).toBe('LR'); - expect(Object.keys(parser.yy.getBranches()).length).toBe(1); - }); - - it('should handle set direction', function() { - const str = 'gitGraph BT:\n' + 'commit\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(Object.keys(commits).length).toBe(1); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getDirection()).toBe('BT'); - expect(Object.keys(parser.yy.getBranches()).length).toBe(1); - }); - - it('should checkout a branch', function() { - const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(Object.keys(commits).length).toBe(0); - expect(parser.yy.getCurrentBranch()).toBe('new'); - }); - - it('should add commits to checked out branch', function() { - const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n' + 'commit\n' + 'commit\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(Object.keys(commits).length).toBe(2); - expect(parser.yy.getCurrentBranch()).toBe('new'); - const branchCommit = parser.yy.getBranches()['new']; - expect(branchCommit).not.toBeNull(); - expect(commits[branchCommit].parent).not.toBeNull(); - }); - it('should handle commit with args', function() { - const str = 'gitGraph:\n' + 'commit "a commit"\n'; - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(Object.keys(commits).length).toBe(1); - const key = Object.keys(commits)[0]; - expect(commits[key].message).toBe('a commit'); - expect(parser.yy.getCurrentBranch()).toBe('master'); - }); - - it('should reset a branch', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'commit\n' + - 'branch newbranch\n' + - 'checkout newbranch\n' + - 'commit\n' + - 'reset master\n'; - - parser.parse(str); - - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(3); - expect(parser.yy.getCurrentBranch()).toBe('newbranch'); - expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']); - expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); - }); - - it('reset can take an argument', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'commit\n' + - 'branch newbranch\n' + - 'checkout newbranch\n' + - 'commit\n' + - 'reset master^\n'; - - parser.parse(str); - - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(3); - expect(parser.yy.getCurrentBranch()).toBe('newbranch'); - const master = commits[parser.yy.getBranches()['master']]; - expect(parser.yy.getHead().id).toEqual(master.parent); - }); - - it('should handle fast forwardable merges', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'branch newbranch\n' + - 'checkout newbranch\n' + - 'commit\n' + - 'commit\n' + - 'checkout master\n' + - 'merge newbranch\n'; - - parser.parse(str); - - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(3); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']); - expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); - }); - - it('should handle cases when merge is a noop', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'branch newbranch\n' + - 'checkout newbranch\n' + - 'commit\n' + - 'commit\n' + - 'merge master\n'; - - parser.parse(str); - - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(3); - expect(parser.yy.getCurrentBranch()).toBe('newbranch'); - expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master']); - expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); - }); - - it('should handle merge with 2 parents', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'branch newbranch\n' + - 'checkout newbranch\n' + - 'commit\n' + - 'commit\n' + - 'checkout master\n' + - 'commit\n' + - 'merge newbranch\n'; - - parser.parse(str); - - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(5); - expect(parser.yy.getCurrentBranch()).toBe('master'); - expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master']); - expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master']); - }); - - it('should handle ff merge when history walk has two parents (merge commit)', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'branch newbranch\n' + - 'checkout newbranch\n' + - 'commit\n' + - 'commit\n' + - 'checkout master\n' + - 'commit\n' + - 'merge newbranch\n' + - 'commit\n' + - 'checkout newbranch\n' + - 'merge master\n'; - - parser.parse(str); - - const commits = parser.yy.getCommits(); - expect(Object.keys(commits).length).toBe(6); - expect(parser.yy.getCurrentBranch()).toBe('newbranch'); - expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']); - expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master']); - - parser.yy.prettyPrint(); - }); - - it('should generate a secure random ID for commits', function() { - const str = 'gitGraph:\n' + 'commit\n' + 'commit\n'; - const EXPECTED_LENGTH = 7; - const EXPECTED_CHARACTERS = '0123456789abcdef'; - - let idCount = 0; - randomString.mockImplementation(options => { - if ( - options.length === EXPECTED_LENGTH && - options.characters === EXPECTED_CHARACTERS && - Object.keys(options).length === 2 - ) { - const id = `abcdef${idCount}`; - idCount += 1; - return id; - } - return 'unexpected-ID'; - }); - - parser.parse(str); - const commits = parser.yy.getCommits(); - - expect(Object.keys(commits)).toEqual(['abcdef0', 'abcdef1']); - Object.keys(commits).forEach(key => { - expect(commits[key].id).toEqual(key); - }); - }); - - it('should generate an array of known branches', function() { - const str = - 'gitGraph:\n' + - 'commit\n' + - 'branch b1\n' + - 'checkout b1\n' + - 'commit\n' + - 'commit\n' + - 'branch b2\n'; - - parser.parse(str); - const branches = gitGraphAst.getBranchesAsObjArray(); - - expect(branches).toHaveLength(3); - expect(branches[0]).toHaveProperty('name', 'master'); - expect(branches[1]).toHaveProperty('name', 'b1'); - expect(branches[2]).toHaveProperty('name', 'b2'); - }); -}); diff --git a/packages/mermaid/src/diagrams/git/parser/gitGraph.jison b/packages/mermaid/src/diagrams/git/parser/gitGraph.jison index 9ff5623f83..2297db17cd 100644 --- a/packages/mermaid/src/diagrams/git/parser/gitGraph.jison +++ b/packages/mermaid/src/diagrams/git/parser/gitGraph.jison @@ -9,10 +9,6 @@ %x string %x options -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %x acc_title %x acc_descr %x acc_descr_multiline @@ -20,11 +16,6 @@ %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } (?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } @@ -76,7 +67,6 @@ checkout(?=\s|$) return 'CHECKOUT'; start : eol start - | directive start | GG document EOF{ return $3; } | GG ':' document EOF{ return $3; } | GG DIR ':' document EOF {yy.setDirection($2); return $4;} @@ -240,27 +230,6 @@ commitType | HIGHLIGHT { $$=yy.commitType.HIGHLIGHT;} ; -directive - : openDirective typeDirective closeDirective - | openDirective typeDirective ':' argDirective closeDirective - ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'gitGraph'); } - ; - ref : ID | STR diff --git a/packages/mermaid/src/diagrams/pie/parser/pie.jison b/packages/mermaid/src/diagrams/pie/parser/pie.jison index e98638aa8b..d1f516e755 100644 --- a/packages/mermaid/src/diagrams/pie/parser/pie.jison +++ b/packages/mermaid/src/diagrams/pie/parser/pie.jison @@ -8,19 +8,10 @@ %x string %x title -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %x acc_title %x acc_descr %x acc_descr_multiline %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; \%\%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */{ /*console.log('');*/ } [\n\r]+ return 'NEWLINE'; @@ -52,7 +43,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili start : eol start - | directive start | PIE document | PIE showData document {yy.setShowData(true);} ; @@ -73,34 +63,12 @@ statement | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } | acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); } | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);} - | directive ; -directive - : openDirective typeDirective closeDirective - | openDirective typeDirective ':' argDirective closeDirective - ; - eol : NEWLINE | ';' | EOF ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'pie'); } - ; - %% diff --git a/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison b/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison index 00c1252944..255b30a035 100644 --- a/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison +++ b/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison @@ -5,10 +5,6 @@ %x string %x md_string %x title -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %x acc_title %x acc_descr %x acc_descr_multiline @@ -16,11 +12,6 @@ %x point_x %x point_y %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; \%\%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */ [\n\r]+ return 'NEWLINE'; @@ -87,7 +78,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} start : eol start | SPACE start - | directive start | QUADRANT document ; @@ -110,7 +100,6 @@ statement | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } | acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); } | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);} - | directive ; points @@ -133,33 +122,12 @@ quadrantDetails | QUADRANT_4 text {yy.setQuadrant4Text($2)} ; -directive - : openDirective typeDirective closeDirective - | openDirective typeDirective ':' argDirective closeDirective - ; - eol : NEWLINE | SEMI | EOF ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'quadrantChart'); } - ; - text: alphaNumToken { $$={text:$1, type: 'text'};} | text textNoTagsToken diff --git a/packages/mermaid/src/diagrams/requirement/parser/requirementDiagram.jison b/packages/mermaid/src/diagrams/requirement/parser/requirementDiagram.jison index 3313102835..6d0f7b1222 100644 --- a/packages/mermaid/src/diagrams/requirement/parser/requirementDiagram.jison +++ b/packages/mermaid/src/diagrams/requirement/parser/requirementDiagram.jison @@ -9,19 +9,10 @@ %x string %x token %x unqString -%x open_directive -%x type_directive -%x arg_directive -%x close_directive %x acc_title %x acc_descr %x acc_descr_multiline %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; "title"\s[^#\n;]+ return 'title'; accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } @@ -99,23 +90,10 @@ start | RD NEWLINE diagram EOF; directive - : openDirective typeDirective closeDirective - | openDirective typeDirective ':' argDirective closeDirective - | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } + : acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } | acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); } | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); }; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); }; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'pie'); }; diagram : /* empty */ { $$ = [] } diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 04f0de20e7..7e2d45419b 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -16,22 +16,15 @@ // A special state for grabbing text up to the first comment/newline %x ID ALIAS LINE -// Directive states -%x open_directive type_directive arg_directive %x acc_title %x acc_descr %x acc_descr_multiline %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; [\n]+ return 'NEWLINE'; \s+ /* skip all whitespace */ ((?!\n)\s)+ /* skip same-line whitespace */ -\#[^\n]* /* skip comments */ +\#[^\n]* /* skip comments */ \%%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */ [0-9]+(?=[ \n]+) return 'NUM'; @@ -106,7 +99,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili start : SPACE start | NEWLINE start - | directive start | SD document { yy.apply($2);return $2; } ; @@ -133,11 +125,6 @@ box_line ; -directive - : openDirective typeDirective closeDirective 'NEWLINE' - | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' - ; - statement : participant_statement | 'create' participant_statement {$2.type='createParticipant'; $$=$2;} @@ -215,7 +202,6 @@ statement $3.unshift({type: 'breakStart', breakText:yy.parseMessage($2), signalType: yy.LINETYPE.BREAK_START}); $3.push({type: 'breakEnd', optText:yy.parseMessage($2), signalType: yy.LINETYPE.BREAK_END}); $$=$3;} - | directive ; option_sections @@ -335,20 +321,4 @@ text2 : TXT {$$ = yy.parseMessage($1.trim().substring(1)) } ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'sequence'); } - ; - %% diff --git a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison index dc050b2ffa..44235ecd40 100644 --- a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison +++ b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison @@ -33,10 +33,6 @@ %x FLOATING_NOTE %x FLOATING_NOTE_ID %x struct -%x open_directive -%x type_directive -%x arg_directive -%x close_directive // A special state for grabbing text up to the first comment/newline %x LINE @@ -50,18 +46,13 @@ .*direction\s+RL[^\n]* return 'direction_rl'; .*direction\s+LR[^\n]* return 'direction_lr'; -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; \%\%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */{ /*console.log('Crap after close');*/ } [\n]+ return 'NL'; [\s]+ /* skip all whitespace */ -((?!\n)\s)+ /* skip same-line whitespace */ -\#[^\n]* /* skip comments */ +((?!\n)\s)+ /* skip same-line whitespace */ +\#[^\n]* /* skip comments */ \%%[^\n]* /* skip comments */ "scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; } \d+ return 'WIDTH'; @@ -155,7 +146,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili start : SPACE start | NL start - | directive start | SD document { /* console.log('--> Root document', $2); */ yy.setRootDoc($2); return $2; } ; @@ -241,7 +231,6 @@ statement $$={ stmt: 'state', id: $3.trim(), note:{position: $2.trim(), text: $4.trim()}}; } | note NOTE_TEXT AS ID - | directive | direction | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } | acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); } @@ -264,10 +253,6 @@ cssClassStatement } ; -directive - : openDirective typeDirective closeDirective - | openDirective typeDirective ':' argDirective closeDirective - ; direction : direction_tb { yy.setDirection('TB');$$={stmt:'dir', value:'TB'};} @@ -308,20 +293,4 @@ notePosition | right_of ; -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'state'); } - ; - %% diff --git a/packages/mermaid/src/diagrams/timeline/parser/timeline.jison b/packages/mermaid/src/diagrams/timeline/parser/timeline.jison index 59b96516a4..348c31fad8 100644 --- a/packages/mermaid/src/diagrams/timeline/parser/timeline.jison +++ b/packages/mermaid/src/diagrams/timeline/parser/timeline.jison @@ -9,17 +9,8 @@ %x acc_descr %x acc_descr_multiline -// Directive states -%x open_directive type_directive arg_directive - - %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; \%%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */ [\n]+ return 'NEWLINE'; @@ -55,7 +46,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili start : timeline document 'EOF' { return $2; } - | directive start ; document @@ -70,11 +60,6 @@ line | EOF { $$=[];} ; -directive - : openDirective typeDirective closeDirective 'NEWLINE' - | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' - ; - statement : title {yy.getCommonDb().setDiagramTitle($1.substr(6));$$=$1.substr(6);} | acc_title acc_title_value { $$=$2.trim();yy.getCommonDb().setAccTitle($$); } @@ -83,7 +68,6 @@ statement | section {yy.addSection($1.substr(8));$$=$1.substr(8);} | period_statement | event_statement - | directive ; period_statement : period {yy.addTask($1,0,'');$$=$1;} @@ -92,21 +76,4 @@ event_statement : event {yy.addEvent($1.substr(2));$$=$1;} ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'timeline'); } - ; - %% diff --git a/packages/mermaid/src/diagrams/user-journey/parser/journey.jison b/packages/mermaid/src/diagrams/user-journey/parser/journey.jison index 4c28d53dc9..5567f14172 100644 --- a/packages/mermaid/src/diagrams/user-journey/parser/journey.jison +++ b/packages/mermaid/src/diagrams/user-journey/parser/journey.jison @@ -9,17 +9,8 @@ %x acc_descr %x acc_descr_multiline -// Directive states -%x open_directive type_directive arg_directive - - %% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; \%%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */ [\n]+ return 'NEWLINE'; @@ -52,7 +43,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili start : journey document 'EOF' { return $2; } - | directive start ; document @@ -67,11 +57,6 @@ line | EOF { $$=[];} ; -directive - : openDirective typeDirective closeDirective 'NEWLINE' - | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' - ; - statement : title {yy.setDiagramTitle($1.substr(6));$$=$1.substr(6);} | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } @@ -79,23 +64,6 @@ statement | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);} | taskName taskData {yy.addTask($1, $2);$$='task';} - | directive - ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'journey'); } ; %% From 066e0967de7e296febc93d17774c8c102d7ac84b Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Sat, 26 Aug 2023 23:27:25 +0530 Subject: [PATCH 4/7] refactor: Move setWrap to individual diagrams as necessary. --- packages/mermaid/src/Diagram.ts | 5 +---- packages/mermaid/src/diagram-api/types.ts | 2 +- packages/mermaid/src/diagrams/c4/c4Diagram.ts | 21 ++++++++++--------- .../src/diagrams/sequence/sequenceDiagram.ts | 3 +++ 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts index 9d665c71c5..3dc9738049 100644 --- a/packages/mermaid/src/Diagram.ts +++ b/packages/mermaid/src/Diagram.ts @@ -47,13 +47,10 @@ export class Diagram { this.db.clear?.(); const config = configApi.getConfig(); this.init?.(config); - // These 2 blocks were added for legacy compatibility. Do not add more such blocks. Use frontmatter instead. + // This block was added for legacy compatibility. Use frontmatter instead of adding more special cases. if (this.metadata.title) { this.db.setDiagramTitle?.(this.metadata.title); } - if (config.wrap) { - this.db.setWrap?.(config.wrap); - } this.parser.parse(this.text); } diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts index e9def2421f..4e73d109f0 100644 --- a/packages/mermaid/src/diagram-api/types.ts +++ b/packages/mermaid/src/diagram-api/types.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { Diagram } from '../Diagram.js'; import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js'; import type * as d3 from 'd3'; @@ -29,7 +30,6 @@ export interface DiagramDB { getAccDescription?: () => string; setDisplayMode?: (title: string) => void; - setWrap?: (wrap: boolean) => void; bindFunctions?: (element: Element) => void; } diff --git a/packages/mermaid/src/diagrams/c4/c4Diagram.ts b/packages/mermaid/src/diagrams/c4/c4Diagram.ts index 4c578b6246..9557a0f708 100644 --- a/packages/mermaid/src/diagrams/c4/c4Diagram.ts +++ b/packages/mermaid/src/diagrams/c4/c4Diagram.ts @@ -1,17 +1,18 @@ // @ts-ignore: JISON doesn't support types -import c4Parser from './parser/c4Diagram.jison'; -import c4Db from './c4Db.js'; -import c4Renderer from './c4Renderer.js'; -import c4Styles from './styles.js'; +import parser from './parser/c4Diagram.jison'; +import db from './c4Db.js'; +import renderer from './c4Renderer.js'; +import styles from './styles.js'; import type { MermaidConfig } from '../../config.type.js'; import type { DiagramDefinition } from '../../diagram-api/types.js'; export const diagram: DiagramDefinition = { - parser: c4Parser, - db: c4Db, - renderer: c4Renderer, - styles: c4Styles, - init: (cnf: MermaidConfig) => { - c4Renderer.setConf(cnf.c4); + parser, + db, + renderer, + styles, + init: ({ c4, wrap }: MermaidConfig) => { + renderer.setConf(c4); + db.setWrap(wrap); }, }; diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts index 8779b9cc43..f8d71c95e6 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts @@ -10,4 +10,7 @@ export const diagram: DiagramDefinition = { db, renderer, styles, + init: ({ wrap }) => { + db.setWrap(wrap); + }, }; From a0e33bee71000b411584e91483f61121920e4cd6 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Tue, 5 Sep 2023 21:57:05 +0530 Subject: [PATCH 5/7] chore: Cleanup gitGraph tests --- .../src/diagrams/git/gitGraphParser.spec.js | 246 ++++++++++++++++++ .../src/diagrams/git/gitGraphParserV2.spec.js | 11 - 2 files changed, 246 insertions(+), 11 deletions(-) create mode 100644 packages/mermaid/src/diagrams/git/gitGraphParser.spec.js diff --git a/packages/mermaid/src/diagrams/git/gitGraphParser.spec.js b/packages/mermaid/src/diagrams/git/gitGraphParser.spec.js new file mode 100644 index 0000000000..446f067393 --- /dev/null +++ b/packages/mermaid/src/diagrams/git/gitGraphParser.spec.js @@ -0,0 +1,246 @@ +import gitGraphAst from './gitGraphAst.js'; +import { parser } from './parser/gitGraph.jison'; + +describe('when parsing a gitGraph', function () { + beforeEach(function () { + parser.yy = gitGraphAst; + parser.yy.clear(); + }); + it('should handle a gitGraph definition', function () { + const str = 'gitGraph:\n' + 'commit\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + + expect(Object.keys(commits).length).toBe(1); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getDirection()).toBe('LR'); + expect(Object.keys(parser.yy.getBranches()).length).toBe(1); + }); + + it('should handle a gitGraph definition with empty options', function () { + const str = 'gitGraph:\n' + 'options\n' + ' end\n' + 'commit\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + + expect(parser.yy.getOptions()).toEqual({}); + expect(Object.keys(commits).length).toBe(1); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getDirection()).toBe('LR'); + expect(Object.keys(parser.yy.getBranches()).length).toBe(1); + }); + + it('should handle a gitGraph definition with valid options', function () { + const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"}\n' + 'end\n' + 'commit\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + expect(parser.yy.getOptions()['key']).toBe('value'); + expect(Object.keys(commits).length).toBe(1); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getDirection()).toBe('LR'); + expect(Object.keys(parser.yy.getBranches()).length).toBe(1); + }); + + it('should not fail on a gitGraph with malformed json', function () { + const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"\n' + 'end\n' + 'commit\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(1); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getDirection()).toBe('LR'); + expect(Object.keys(parser.yy.getBranches()).length).toBe(1); + }); + + it('should handle set direction', function () { + const str = 'gitGraph TB:\n' + 'commit\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + + expect(Object.keys(commits).length).toBe(1); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getDirection()).toBe('TB'); + expect(Object.keys(parser.yy.getBranches()).length).toBe(1); + }); + + it('should checkout a branch', function () { + const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + + expect(Object.keys(commits).length).toBe(0); + expect(parser.yy.getCurrentBranch()).toBe('new'); + }); + + it('should add commits to checked out branch', function () { + const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n' + 'commit\n' + 'commit\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + + expect(Object.keys(commits).length).toBe(2); + expect(parser.yy.getCurrentBranch()).toBe('new'); + const branchCommit = parser.yy.getBranches()['new']; + expect(branchCommit).not.toBeNull(); + expect(commits[branchCommit].parent).not.toBeNull(); + }); + it('should handle commit with args', function () { + const str = 'gitGraph:\n' + 'commit "a commit"\n'; + + parser.parse(str); + const commits = parser.yy.getCommits(); + + expect(Object.keys(commits).length).toBe(1); + const key = Object.keys(commits)[0]; + expect(commits[key].message).toBe('a commit'); + expect(parser.yy.getCurrentBranch()).toBe('main'); + }); + + // Reset has been commented out in JISON + it.skip('should reset a branch', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'commit\n' + + 'branch newbranch\n' + + 'checkout newbranch\n' + + 'commit\n' + + 'reset main\n'; + + parser.parse(str); + + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(3); + expect(parser.yy.getCurrentBranch()).toBe('newbranch'); + expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['main']); + expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); + }); + + it.skip('reset can take an argument', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'commit\n' + + 'branch newbranch\n' + + 'checkout newbranch\n' + + 'commit\n' + + 'reset main^\n'; + + parser.parse(str); + + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(3); + expect(parser.yy.getCurrentBranch()).toBe('newbranch'); + const main = commits[parser.yy.getBranches()['main']]; + expect(parser.yy.getHead().id).toEqual(main.parent); + }); + + it.skip('should handle fast forwardable merges', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'branch newbranch\n' + + 'checkout newbranch\n' + + 'commit\n' + + 'commit\n' + + 'checkout main\n' + + 'merge newbranch\n'; + + parser.parse(str); + + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(4); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['main']); + expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); + }); + + it('should handle cases when merge is a noop', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'branch newbranch\n' + + 'checkout newbranch\n' + + 'commit\n' + + 'commit\n' + + 'merge main\n'; + + parser.parse(str); + + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(4); + expect(parser.yy.getCurrentBranch()).toBe('newbranch'); + expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['main']); + expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); + }); + + it('should handle merge with 2 parents', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'branch newbranch\n' + + 'checkout newbranch\n' + + 'commit\n' + + 'commit\n' + + 'checkout main\n' + + 'commit\n' + + 'merge newbranch\n'; + + parser.parse(str); + + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(5); + expect(parser.yy.getCurrentBranch()).toBe('main'); + expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['main']); + expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['main']); + }); + + it.skip('should handle ff merge when history walk has two parents (merge commit)', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'branch newbranch\n' + + 'checkout newbranch\n' + + 'commit\n' + + 'commit\n' + + 'checkout main\n' + + 'commit\n' + + 'merge newbranch\n' + + 'commit\n' + + 'checkout newbranch\n' + + 'merge main\n'; + + parser.parse(str); + + const commits = parser.yy.getCommits(); + expect(Object.keys(commits).length).toBe(7); + expect(parser.yy.getCurrentBranch()).toBe('newbranch'); + expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['main']); + expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['main']); + + parser.yy.prettyPrint(); + }); + + it('should generate an array of known branches', function () { + const str = + 'gitGraph:\n' + + 'commit\n' + + 'branch b1\n' + + 'checkout b1\n' + + 'commit\n' + + 'commit\n' + + 'branch b2\n'; + + parser.parse(str); + const branches = gitGraphAst.getBranchesAsObjArray(); + + expect(branches).toHaveLength(3); + expect(branches[0]).toHaveProperty('name', 'main'); + expect(branches[1]).toHaveProperty('name', 'b1'); + expect(branches[2]).toHaveProperty('name', 'b2'); + }); +}); diff --git a/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js b/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js index 764fbb214c..df20a5eb5a 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js +++ b/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js @@ -1,22 +1,11 @@ -/* eslint-env jasmine */ -// Todo reintroduce without cryptoRandomString import gitGraphAst from './gitGraphAst.js'; import { parser } from './parser/gitGraph.jison'; -//import randomString from 'crypto-random-string'; -//import cryptoRandomString from 'crypto-random-string'; - -//jest.mock('crypto-random-string'); describe('when parsing a gitGraph', function () { - let randomNumber; beforeEach(function () { parser.yy = gitGraphAst; parser.yy.clear(); - randomNumber = 0; }); - // afterEach(function() { - // cryptoRandomString.mockReset(); - // }); it('should handle a gitGraph commit with NO pararms, get auto-genrated reandom ID', function () { const str = `gitGraph: commit From 18079a44bedb09eda7831aaacc5c3b263c97c227 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Wed, 6 Sep 2023 22:06:05 +0530 Subject: [PATCH 6/7] chore: Add deprecation notices, improve types Co-authored-by: Alois Klink --- docs/config/setup/modules/mermaidAPI.md | 2 +- packages/mermaid/src/Diagram.ts | 7 ++++--- packages/mermaid/src/diagram-api/diagramAPI.ts | 3 ++- packages/mermaid/src/diagram-api/types.ts | 7 +++++++ packages/mermaid/src/mermaidAPI.ts | 2 +- packages/mermaid/src/preprocess.ts | 11 +++++++++-- 6 files changed, 24 insertions(+), 8 deletions(-) diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index 3c2b5bb84a..1ea19fac4e 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -31,7 +31,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi) ### mermaidAPI -• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`, `metadata`: { `title?`: `string` }) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }> +• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`, `metadata`: `Pick`<`DiagramMetadata`, `"title"`>) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }> ## mermaidAPI configuration defaults diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts index 3dc9738049..b77091f28c 100644 --- a/packages/mermaid/src/Diagram.ts +++ b/packages/mermaid/src/Diagram.ts @@ -4,7 +4,7 @@ import { getDiagram, registerDiagram } from './diagram-api/diagramAPI.js'; import { detectType, getDiagramLoader } from './diagram-api/detectType.js'; import { UnknownDiagramError } from './errors.js'; import type { DetailedError } from './utils.js'; -import type { DiagramDefinition } from './diagram-api/types.js'; +import type { DiagramDefinition, DiagramMetadata } from './diagram-api/types.js'; export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void; @@ -20,7 +20,7 @@ export class Diagram { private init?: DiagramDefinition['init']; private detectError?: UnknownDiagramError; - constructor(public text: string, public metadata: { title?: string } = {}) { + constructor(public text: string, public metadata: Pick = {}) { this.text += '\n'; const cnf = configApi.getConfig(); try { @@ -72,13 +72,14 @@ export class Diagram { * **Warning:** This function may be changed in the future. * @alpha * @param text - The mermaid diagram definition. + * @param metadata - Diagram metadata, defined in YAML. * @returns A the Promise of a Diagram object. * @throws {@link UnknownDiagramError} if the diagram type can not be found. * @privateRemarks This is exported as part of the public mermaidAPI. */ export const getDiagramFromText = async ( text: string, - metadata: { title?: string } = {} + metadata: Pick = {} ): Promise => { const type = detectType(text, configApi.getConfig()); try { diff --git a/packages/mermaid/src/diagram-api/diagramAPI.ts b/packages/mermaid/src/diagram-api/diagramAPI.ts index 9a4af2585d..ea3c10159f 100644 --- a/packages/mermaid/src/diagram-api/diagramAPI.ts +++ b/packages/mermaid/src/diagram-api/diagramAPI.ts @@ -57,7 +57,8 @@ export const registerDiagram = ( setupGraphViewbox, getCommonDb(), () => { - // parseDirective is removed. This is a no-op for legacy support. + // parseDirective is removed in https://github.com/mermaid-js/mermaid/pull/4759. + // This is a no-op for legacy support. } ); }; diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts index 4e73d109f0..15aa4b0336 100644 --- a/packages/mermaid/src/diagram-api/types.ts +++ b/packages/mermaid/src/diagram-api/types.ts @@ -3,6 +3,11 @@ import type { Diagram } from '../Diagram.js'; import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js'; import type * as d3 from 'd3'; +export interface DiagramMetadata { + title?: string; + config?: MermaidConfig; +} + export interface InjectUtils { _log: any; _setLogLevel: any; @@ -10,6 +15,7 @@ export interface InjectUtils { _sanitizeText: any; _setupGraphViewbox: any; _commonDb: any; + /** @deprecated as directives will be pre-processed since https://github.com/mermaid-js/mermaid/pull/4759 */ _parseDirective: any; } @@ -46,6 +52,7 @@ export interface DiagramDefinition { _sanitizeText: InjectUtils['_sanitizeText'], _setupGraphViewbox: InjectUtils['_setupGraphViewbox'], _commonDb: InjectUtils['_commonDb'], + /** @deprecated as directives will be pre-processed since https://github.com/mermaid-js/mermaid/pull/4759 */ _parseDirective: InjectUtils['_parseDirective'] ) => void; } diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index f71fe27a76..3649b50f53 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -99,7 +99,7 @@ export interface RenderResult { function processAndSetConfigs(text: string) { const processed = preprocessDiagram(text); configApi.reset(); - configApi.addDirective(processed.config); + configApi.addDirective(processed.config ?? {}); return processed; } diff --git a/packages/mermaid/src/preprocess.ts b/packages/mermaid/src/preprocess.ts index 3c33ce30f8..6e386e744a 100644 --- a/packages/mermaid/src/preprocess.ts +++ b/packages/mermaid/src/preprocess.ts @@ -1,5 +1,6 @@ import { cleanupComments } from './diagram-api/comments.js'; import { extractFrontMatter } from './diagram-api/frontmatter.js'; +import type { DiagramMetadata } from './diagram-api/types.js'; import utils, { cleanAndMerge, removeDirectives } from './utils.js'; const cleanupText = (code: string) => { @@ -44,7 +45,13 @@ const processDirectives = (code: string) => { }; }; -export const preprocessDiagram = (code: string) => { +/** + * Preprocess the given code by cleaning it up, extracting front matter and directives, + * cleaning and merging configuration, and removing comments. + * @param code - The code to preprocess. + * @returns The object containing the preprocessed code, title, and configuration. + */ +export function preprocessDiagram(code: string): DiagramMetadata & { code: string } { const cleanedCode = cleanupText(code); const frontMatterResult = processFrontmatter(cleanedCode); const directiveResult = processDirectives(frontMatterResult.text); @@ -55,4 +62,4 @@ export const preprocessDiagram = (code: string) => { title: frontMatterResult.title, config, }; -}; +} From 667b17bf66ad13fe822a92f43d62265ba4f9fccb Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Thu, 7 Sep 2023 10:15:58 +0530 Subject: [PATCH 7/7] Remove unnecessary tests --- .../diagrams/sequence/sequenceDiagram.spec.js | 83 +------------------ 1 file changed, 1 insertion(+), 82 deletions(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index c46c195412..77ac7c45cd 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -1,5 +1,4 @@ import { vi } from 'vitest'; - import * as configApi from '../../config.js'; import mermaidAPI from '../../mermaidAPI.js'; import { Diagram, getDiagramFromText } from '../../Diagram.js'; @@ -225,6 +224,7 @@ Bob-->Alice: I am good thanks!`; diagram.renderer.draw(str, 'tst', '1.2.3', diagram); // needs to be rendered for the correct value of visibility auto numbers expect(diagram.db.showSequenceNumbers()).toBe(true); }); + it('should handle a sequenceDiagram definition with a title:', async () => { const str = ` sequenceDiagram @@ -2034,84 +2034,3 @@ participant Alice`; }); }); }); - -describe('when rendering a sequenceDiagram with directives', () => { - beforeAll(function () { - let conf = { - diagramMarginX: 50, - diagramMarginY: 10, - actorMargin: 50, - width: 150, - height: 65, - boxMargin: 10, - messageMargin: 40, - boxTextMargin: 15, - noteMargin: 25, - }; - mermaidAPI.initialize({ sequence: conf }); - }); - - beforeEach(function () { - mermaidAPI.reset(); - diagram.renderer.bounds.init(); - }); - - it('should handle one actor, when theme is dark and logLevel is 1 DX1 (dfg1)', async () => { - const str = ` -sequenceDiagram -participant Alice -`; - diagram = new Diagram(str); - diagram.renderer.bounds.init(); - diagram.renderer.draw(str, 'tst', '1.2.3', diagram); - - const { bounds, models } = diagram.renderer.bounds.getBounds(); - const mermaid = mermaidAPI.getConfig(); - expect(bounds.startx).toBe(0); - expect(bounds.startx).toBe(0); - expect(bounds.starty).toBe(0); - expect(bounds.stopy).toBe( - models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin - ); - }); - it('should handle one actor, when logLevel is 3 (dfg0)', async () => { - const str = ` -sequenceDiagram -participant Alice -`; - - diagram = new Diagram(str); - diagram.renderer.draw(str, 'tst', '1.2.3', diagram); - - const { bounds, models } = diagram.renderer.bounds.getBounds(); - const mermaid = mermaidAPI.getConfig(); - expect(bounds.startx).toBe(0); - expect(bounds.startx).toBe(0); - expect(bounds.starty).toBe(0); - expect(bounds.stopy).toBe( - models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin - ); - }); - it('should hide sequence numbers when autonumber is removed when autonumber is enabled', async () => { - const str1 = ` -sequenceDiagram -autonumber -Alice->Bob:Hello Bob, how are you? -Note right of Bob: Bob thinks -Bob-->Alice: I am good thanks!`; - - diagram = new Diagram(str1); - diagram.renderer.draw(str1, 'tst', '1.2.3', diagram); // needs to be rendered for the correct value of visibility auto numbers - expect(diagram.db.showSequenceNumbers()).toBe(true); - - const str2 = ` -sequenceDiagram -Alice->Bob:Hello Bob, how are you? -Note right of Bob: Bob thinks -Bob-->Alice: I am good thanks!`; - - diagram = new Diagram(str2); - diagram.renderer.draw(str2, 'tst', '1.2.3', diagram); - expect(diagram.db.showSequenceNumbers()).toBe(false); - }); -});