From 1a50a326cb495b6ef9a429b9d4c0a52210c02aec Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Tue, 22 Aug 2023 11:38:25 +0530 Subject: [PATCH] refactor: Remove circular dependencies --- cSpell.json | 1 + packages/mermaid/.madgerc | 22 +++++++++ .../mermaid/src/diagram-api/detectType.ts | 46 +++---------------- .../mermaid/src/diagram-api/diagramAPI.ts | 2 +- .../mermaid/src/diagram-api/frontmatter.ts | 10 +--- .../mermaid/src/diagram-api/loadDiagram.ts | 36 +++++++++++++++ packages/mermaid/src/diagram-api/regexes.ts | 11 +++++ packages/mermaid/src/docs/package.json | 3 +- packages/mermaid/src/mermaid.ts | 7 +-- packages/mermaid/src/utils.ts | 9 ++-- pnpm-lock.yaml | 6 +++ 11 files changed, 94 insertions(+), 59 deletions(-) create mode 100644 packages/mermaid/.madgerc create mode 100644 packages/mermaid/src/diagram-api/loadDiagram.ts create mode 100644 packages/mermaid/src/diagram-api/regexes.ts diff --git a/cSpell.json b/cSpell.json index af7a9ca46..9e10e41d0 100644 --- a/cSpell.json +++ b/cSpell.json @@ -106,6 +106,7 @@ "rects", "reda", "redmine", + "regexes", "rehype", "roledescription", "rozhkov", diff --git a/packages/mermaid/.madgerc b/packages/mermaid/.madgerc new file mode 100644 index 000000000..1a558d9e6 --- /dev/null +++ b/packages/mermaid/.madgerc @@ -0,0 +1,22 @@ +{ + "detectiveOptions": { + "ts": { + "skipTypeImports": true + }, + "es6": { + "skipTypeImports": true + } + }, + "fileExtensions": [ + "js", + "ts" + ], + "excludeRegExp": [ + "node_modules", + "docs", + "vitepress", + "detector", + "Detector" + ], + "tsConfig": "./tsconfig.json" +} diff --git a/packages/mermaid/src/diagram-api/detectType.ts b/packages/mermaid/src/diagram-api/detectType.ts index 8351a67df..aae913155 100644 --- a/packages/mermaid/src/diagram-api/detectType.ts +++ b/packages/mermaid/src/diagram-api/detectType.ts @@ -6,14 +6,10 @@ import type { DiagramLoader, ExternalDiagramDefinition, } from './types.js'; -import { frontMatterRegex } from './frontmatter.js'; -import { getDiagram, registerDiagram } from './diagramAPI.js'; +import { anyCommentRegex, directiveRegex, frontMatterRegex } from './regexes.js'; import { UnknownDiagramError } from '../errors.js'; -const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; -const anyComment = /\s*%%.*\n/gm; - -const detectors: Record = {}; +export const detectors: Record = {}; /** * Detects the type of the graph text. @@ -38,7 +34,10 @@ const detectors: Record = {}; * @returns A graph definition key */ export const detectType = function (text: string, config?: MermaidConfig): string { - text = text.replace(frontMatterRegex, '').replace(directive, '').replace(anyComment, '\n'); + text = text + .replace(frontMatterRegex, '') + .replace(directiveRegex, '') + .replace(anyCommentRegex, '\n'); for (const [key, { detector }] of Object.entries(detectors)) { const diagram = detector(text, config); if (diagram) { @@ -70,39 +69,6 @@ export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinitio } }; -export const loadRegisteredDiagrams = async () => { - log.debug(`Loading registered diagrams`); - // Load all lazy loaded diagrams in parallel - const results = await Promise.allSettled( - Object.entries(detectors).map(async ([key, { detector, loader }]) => { - if (loader) { - try { - getDiagram(key); - } catch (error) { - try { - // Register diagram if it is not already registered - const { diagram, id } = await loader(); - registerDiagram(id, diagram, detector); - } catch (err) { - // Remove failed diagram from detectors - log.error(`Failed to load external diagram with key ${key}. Removing from detectors.`); - delete detectors[key]; - throw err; - } - } - } - }) - ); - const failed = results.filter((result) => result.status === 'rejected'); - if (failed.length > 0) { - log.error(`Failed to load ${failed.length} external diagrams`); - for (const res of failed) { - log.error(res); - } - throw new Error(`Failed to load ${failed.length} external diagrams`); - } -}; - export const addDetector = (key: string, detector: DiagramDetector, loader?: DiagramLoader) => { if (detectors[key]) { log.error(`Detector with key ${key} already exists`); diff --git a/packages/mermaid/src/diagram-api/diagramAPI.ts b/packages/mermaid/src/diagram-api/diagramAPI.ts index 3edd982bb..00da66ffe 100644 --- a/packages/mermaid/src/diagram-api/diagramAPI.ts +++ b/packages/mermaid/src/diagram-api/diagramAPI.ts @@ -4,7 +4,7 @@ import { getConfig as _getConfig } from '../config.js'; import { sanitizeText as _sanitizeText } from '../diagrams/common/common.js'; import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox.js'; import { addStylesForDiagram } from '../styles.js'; -import { DiagramDefinition, DiagramDetector } from './types.js'; +import type { DiagramDefinition, DiagramDetector } from './types.js'; import * as _commonDb from '../commonDb.js'; import { parseDirective as _parseDirective } from '../directiveUtils.js'; diff --git a/packages/mermaid/src/diagram-api/frontmatter.ts b/packages/mermaid/src/diagram-api/frontmatter.ts index f8d2e9c41..ab0948737 100644 --- a/packages/mermaid/src/diagram-api/frontmatter.ts +++ b/packages/mermaid/src/diagram-api/frontmatter.ts @@ -1,14 +1,8 @@ -import { DiagramDB } from './types.js'; +import type { DiagramDB } from './types.js'; +import { frontMatterRegex } from './regexes.js'; // The "* as yaml" part is necessary for tree-shaking import * as yaml from 'js-yaml'; -// Match Jekyll-style front matter blocks (https://jekyllrb.com/docs/front-matter/). -// Based on regex used by Jekyll: https://github.com/jekyll/jekyll/blob/6dd3cc21c40b98054851846425af06c64f9fb466/lib/jekyll/document.rb#L10 -// Note that JS doesn't support the "\A" anchor, which means we can't use -// multiline mode. -// Relevant YAML spec: https://yaml.org/spec/1.2.2/#914-explicit-documents -export const frontMatterRegex = /^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s; - type FrontMatterMetadata = { title?: string; // Allows custom display modes. Currently used for compact mode in gantt charts. diff --git a/packages/mermaid/src/diagram-api/loadDiagram.ts b/packages/mermaid/src/diagram-api/loadDiagram.ts new file mode 100644 index 000000000..c1b445bf6 --- /dev/null +++ b/packages/mermaid/src/diagram-api/loadDiagram.ts @@ -0,0 +1,36 @@ +import { log } from '../logger.js'; +import { detectors } from './detectType.js'; +import { getDiagram, registerDiagram } from './diagramAPI.js'; + +export const loadRegisteredDiagrams = async () => { + log.debug(`Loading registered diagrams`); + // Load all lazy loaded diagrams in parallel + const results = await Promise.allSettled( + Object.entries(detectors).map(async ([key, { detector, loader }]) => { + if (loader) { + try { + getDiagram(key); + } catch (error) { + try { + // Register diagram if it is not already registered + const { diagram, id } = await loader(); + registerDiagram(id, diagram, detector); + } catch (err) { + // Remove failed diagram from detectors + log.error(`Failed to load external diagram with key ${key}. Removing from detectors.`); + delete detectors[key]; + throw err; + } + } + } + }) + ); + const failed = results.filter((result) => result.status === 'rejected'); + if (failed.length > 0) { + log.error(`Failed to load ${failed.length} external diagrams`); + for (const res of failed) { + log.error(res); + } + throw new Error(`Failed to load ${failed.length} external diagrams`); + } +}; diff --git a/packages/mermaid/src/diagram-api/regexes.ts b/packages/mermaid/src/diagram-api/regexes.ts new file mode 100644 index 000000000..bb688b9c2 --- /dev/null +++ b/packages/mermaid/src/diagram-api/regexes.ts @@ -0,0 +1,11 @@ +// Match Jekyll-style front matter blocks (https://jekyllrb.com/docs/front-matter/). +// Based on regex used by Jekyll: https://github.com/jekyll/jekyll/blob/6dd3cc21c40b98054851846425af06c64f9fb466/lib/jekyll/document.rb#L10 +// Note that JS doesn't support the "\A" anchor, which means we can't use +// multiline mode. +// Relevant YAML spec: https://yaml.org/spec/1.2.2/#914-explicit-documents +export const frontMatterRegex = /^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s; + +export const directiveRegex = + /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; + +export const anyCommentRegex = /\s*%%.*\n/gm; diff --git a/packages/mermaid/src/docs/package.json b/packages/mermaid/src/docs/package.json index a5cb5859c..94ab7a535 100644 --- a/packages/mermaid/src/docs/package.json +++ b/packages/mermaid/src/docs/package.json @@ -17,7 +17,8 @@ "dependencies": { "@vueuse/core": "^10.1.0", "jiti": "^1.18.2", - "vue": "^3.3" + "vue": "^3.3", + "mermaid": "workspace:^" }, "devDependencies": { "@iconify-json/carbon": "^1.1.16", diff --git a/packages/mermaid/src/mermaid.ts b/packages/mermaid/src/mermaid.ts index 30297e7b5..d140ded4f 100644 --- a/packages/mermaid/src/mermaid.ts +++ b/packages/mermaid/src/mermaid.ts @@ -7,11 +7,8 @@ import { MermaidConfig } from './config.type.js'; import { log } from './logger.js'; import utils from './utils.js'; import { mermaidAPI, ParseOptions, RenderResult } from './mermaidAPI.js'; -import { - registerLazyLoadedDiagrams, - loadRegisteredDiagrams, - detectType, -} from './diagram-api/detectType.js'; +import { registerLazyLoadedDiagrams, detectType } from './diagram-api/detectType.js'; +import { loadRegisteredDiagrams } from './diagram-api/loadDiagram.js'; import type { ParseErrorFunction } from './Diagram.js'; import { isDetailedError } from './utils.js'; import type { DetailedError } from './utils.js'; diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts index 937f3f8f8..0c5eca2ae 100644 --- a/packages/mermaid/src/utils.ts +++ b/packages/mermaid/src/utils.ts @@ -32,6 +32,7 @@ import assignWithDepth from './assignWithDepth.js'; import { MermaidConfig } from './config.type.js'; import memoize from 'lodash-es/memoize.js'; import merge from 'lodash-es/merge.js'; +import { directiveRegex } from './diagram-api/regexes.js'; export const ZERO_WIDTH_SPACE = '\u200b'; @@ -58,7 +59,7 @@ const d3CurveTypes = { curveStepAfter: curveStepAfter, curveStepBefore: curveStepBefore, }; -const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; + const directiveWithoutOpen = /\s*(?:(\w+)(?=:):|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; @@ -163,10 +164,10 @@ export const detectDirective = function ( ); let match; const result = []; - while ((match = directive.exec(text)) !== null) { + while ((match = directiveRegex.exec(text)) !== null) { // This is necessary to avoid infinite loops with zero-width matches - if (match.index === directive.lastIndex) { - directive.lastIndex++; + if (match.index === directiveRegex.lastIndex) { + directiveRegex.lastIndex++; } if ( (match && !type) || diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8dc7ec846..42a336739 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -434,6 +434,9 @@ importers: jiti: specifier: ^1.18.2 version: 1.18.2 + mermaid: + specifier: workspace:^ + version: link:../.. vue: specifier: ^3.3 version: 3.3.4 @@ -486,6 +489,9 @@ importers: jiti: specifier: ^1.18.2 version: 1.18.2 + mermaid: + specifier: workspace:^ + version: link:../.. vue: specifier: ^3.3 version: 3.3.4