Skip to content

Commit

Permalink
Merge pull request #4761 from mermaid-js/sidv/RemoveCircularDeps
Browse files Browse the repository at this point in the history
Remove Circular Dependencies
  • Loading branch information
knsv authored Aug 22, 2023
2 parents 31c0a0c + 52f0555 commit 6e0f411
Show file tree
Hide file tree
Showing 13 changed files with 111 additions and 61 deletions.
18 changes: 16 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,22 @@ jobs:
ERROR_MESSAGE+=' `pnpm run --filter mermaid types:build-config`'
ERROR_MESSAGE+=' on your local machine.'
echo "::error title=Lint failure::${ERROR_MESSAGE}"
# make sure to return an error exitcode so that GitHub actions shows a red-cross
exit 1
# make sure to return an error exitcode so that GitHub actions shows a red-cross
exit 1
fi
- name: Verify no circular dependencies
working-directory: ./packages/mermaid
shell: bash
run: |
if ! pnpm run --filter mermaid checkCircle; then
ERROR_MESSAGE='Circular dependency detected.'
ERROR_MESSAGE+=' This should be fixed by removing the circular dependency.'
ERROR_MESSAGE+=' Run `pnpm run --filter mermaid checkCircle` on your local machine'
ERROR_MESSAGE+=' to see the circular dependency.'
echo "::error title=Lint failure::${ERROR_MESSAGE}"
# make sure to return an error exitcode so that GitHub actions shows a red-cross
exit 1
fi
- name: Verify Docs
Expand Down
1 change: 1 addition & 0 deletions cSpell.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"rects",
"reda",
"redmine",
"regexes",
"rehype",
"roledescription",
"rozhkov",
Expand Down
22 changes: 22 additions & 0 deletions packages/mermaid/.madgerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"detectiveOptions": {
"ts": {
"skipTypeImports": true
},
"es6": {
"skipTypeImports": true
}
},
"fileExtensions": [
"js",
"ts"
],
"excludeRegExp": [
"node_modules",
"docs",
"vitepress",
"detector",
"Detector"
],
"tsConfig": "./tsconfig.json"
}
1 change: 1 addition & 0 deletions packages/mermaid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"docs:verify-version": "ts-node-esm scripts/update-release-version.mts --verify",
"types:build-config": "ts-node-esm --transpileOnly scripts/create-types-from-json-schema.mts",
"types:verify-config": "ts-node-esm scripts/create-types-from-json-schema.mts --verify",
"checkCircle": "npx madge --circular ./src",
"release": "pnpm build",
"prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm -w run build"
},
Expand Down
46 changes: 6 additions & 40 deletions packages/mermaid/src/diagram-api/detectType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, DetectorRecord> = {};
export const detectors: Record<string, DetectorRecord> = {};

/**
* Detects the type of the graph text.
Expand All @@ -38,7 +34,10 @@ const detectors: Record<string, DetectorRecord> = {};
* @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) {
Expand Down Expand Up @@ -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`);
Expand Down
2 changes: 1 addition & 1 deletion packages/mermaid/src/diagram-api/diagramAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
10 changes: 2 additions & 8 deletions packages/mermaid/src/diagram-api/frontmatter.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
36 changes: 36 additions & 0 deletions packages/mermaid/src/diagram-api/loadDiagram.ts
Original file line number Diff line number Diff line change
@@ -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`);
}
};
11 changes: 11 additions & 0 deletions packages/mermaid/src/diagram-api/regexes.ts
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 2 additions & 1 deletion packages/mermaid/src/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 2 additions & 5 deletions packages/mermaid/src/mermaid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
9 changes: 5 additions & 4 deletions packages/mermaid/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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;

Expand Down Expand Up @@ -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) ||
Expand Down
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6e0f411

Please sign in to comment.