Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Circular Dependencies #4761

Merged
merged 3 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, '')
Comment on lines +37 to +38

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data

This [regular expression](1) that depends on [library input](2) may run slow on strings with many repetitions of '\n '. This [regular expression](1) that depends on [library input](3) may run slow on strings with many repetitions of '\n '. This [regular expression](1) that depends on [library input](4) may run slow on strings with many repetitions of '\n '. This [regular expression](1) that depends on [library input](5) may run slow on strings with many repetitions of '\n '. This [regular expression](1) that depends on [library input](6) may run slow on strings with many repetitions of '\n '.
.replace(directiveRegex, '')
.replace(anyCommentRegex, '\n');
Comment on lines +37 to +40

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data

This [regular expression](1) that depends on [library input](2) may run slow on strings with many repetitions of ' '. This [regular expression](3) that depends on [library input](2) may run slow on strings starting with '%%' and with many repetitions of '%%'. This [regular expression](1) that depends on [library input](4) may run slow on strings with many repetitions of ' '. This [regular expression](3) that depends on [library input](4) may run slow on strings starting with '%%' and with many repetitions of '%%'. This [regular expression](1) that depends on [library input](5) may run slow on strings with many repetitions of ' '. This [regular expression](3) that depends on [library input](5) may run slow on strings starting with '%%' and with many repetitions of '%%'. This [regular expression](1) that depends on [library input](6) may run slow on strings with many repetitions of ' '. This [regular expression](3) that depends on [library input](6) may run slow on strings starting with '%%' and with many repetitions of '%%'. This [regular expression](1) that depends on [library input](7) may run slow on strings with many repetitions of ' '. This [regular expression](3) that depends on [library input](7) may run slow on strings starting with '%%' and with many repetitions of '%%'.
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.