Merge pull request #4761 from mermaid-js/sidv/RemoveCircularDeps

Remove Circular Dependencies
This commit is contained in:
Knut Sveidqvist 2023-08-22 07:37:32 +00:00 committed by GitHub
commit 6e0f41180f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 111 additions and 61 deletions

View File

@ -62,8 +62,22 @@ jobs:
ERROR_MESSAGE+=' `pnpm run --filter mermaid types:build-config`' ERROR_MESSAGE+=' `pnpm run --filter mermaid types:build-config`'
ERROR_MESSAGE+=' on your local machine.' ERROR_MESSAGE+=' on your local machine.'
echo "::error title=Lint failure::${ERROR_MESSAGE}" echo "::error title=Lint failure::${ERROR_MESSAGE}"
# make sure to return an error exitcode so that GitHub actions shows a red-cross # make sure to return an error exitcode so that GitHub actions shows a red-cross
exit 1 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 fi
- name: Verify Docs - name: Verify Docs

View File

@ -106,6 +106,7 @@
"rects", "rects",
"reda", "reda",
"redmine", "redmine",
"regexes",
"rehype", "rehype",
"roledescription", "roledescription",
"rozhkov", "rozhkov",

22
packages/mermaid/.madgerc Normal file
View File

@ -0,0 +1,22 @@
{
"detectiveOptions": {
"ts": {
"skipTypeImports": true
},
"es6": {
"skipTypeImports": true
}
},
"fileExtensions": [
"js",
"ts"
],
"excludeRegExp": [
"node_modules",
"docs",
"vitepress",
"detector",
"Detector"
],
"tsConfig": "./tsconfig.json"
}

View File

@ -38,6 +38,7 @@
"docs:verify-version": "ts-node-esm scripts/update-release-version.mts --verify", "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: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", "types:verify-config": "ts-node-esm scripts/create-types-from-json-schema.mts --verify",
"checkCircle": "npx madge --circular ./src",
"release": "pnpm build", "release": "pnpm build",
"prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm -w run build" "prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm -w run build"
}, },

View File

@ -6,14 +6,10 @@ import type {
DiagramLoader, DiagramLoader,
ExternalDiagramDefinition, ExternalDiagramDefinition,
} from './types.js'; } from './types.js';
import { frontMatterRegex } from './frontmatter.js'; import { anyCommentRegex, directiveRegex, frontMatterRegex } from './regexes.js';
import { getDiagram, registerDiagram } from './diagramAPI.js';
import { UnknownDiagramError } from '../errors.js'; import { UnknownDiagramError } from '../errors.js';
const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; export const detectors: Record<string, DetectorRecord> = {};
const anyComment = /\s*%%.*\n/gm;
const detectors: Record<string, DetectorRecord> = {};
/** /**
* Detects the type of the graph text. * Detects the type of the graph text.
@ -38,7 +34,10 @@ const detectors: Record<string, DetectorRecord> = {};
* @returns A graph definition key * @returns A graph definition key
*/ */
export const detectType = function (text: string, config?: MermaidConfig): string { 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)) { for (const [key, { detector }] of Object.entries(detectors)) {
const diagram = detector(text, config); const diagram = detector(text, config);
if (diagram) { 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) => { export const addDetector = (key: string, detector: DiagramDetector, loader?: DiagramLoader) => {
if (detectors[key]) { if (detectors[key]) {
log.error(`Detector with key ${key} already exists`); log.error(`Detector with key ${key} already exists`);

View File

@ -4,7 +4,7 @@ import { getConfig as _getConfig } from '../config.js';
import { sanitizeText as _sanitizeText } from '../diagrams/common/common.js'; import { sanitizeText as _sanitizeText } from '../diagrams/common/common.js';
import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox.js'; import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox.js';
import { addStylesForDiagram } from '../styles.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 * as _commonDb from '../commonDb.js';
import { parseDirective as _parseDirective } from '../directiveUtils.js'; import { parseDirective as _parseDirective } from '../directiveUtils.js';

View File

@ -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 // The "* as yaml" part is necessary for tree-shaking
import * as yaml from 'js-yaml'; 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 = { type FrontMatterMetadata = {
title?: string; title?: string;
// Allows custom display modes. Currently used for compact mode in gantt charts. // Allows custom display modes. Currently used for compact mode in gantt charts.

View File

@ -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`);
}
};

View File

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

View File

@ -17,7 +17,8 @@
"dependencies": { "dependencies": {
"@vueuse/core": "^10.1.0", "@vueuse/core": "^10.1.0",
"jiti": "^1.18.2", "jiti": "^1.18.2",
"vue": "^3.3" "vue": "^3.3",
"mermaid": "workspace:^"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/carbon": "^1.1.16", "@iconify-json/carbon": "^1.1.16",

View File

@ -7,11 +7,8 @@ import { MermaidConfig } from './config.type.js';
import { log } from './logger.js'; import { log } from './logger.js';
import utils from './utils.js'; import utils from './utils.js';
import { mermaidAPI, ParseOptions, RenderResult } from './mermaidAPI.js'; import { mermaidAPI, ParseOptions, RenderResult } from './mermaidAPI.js';
import { import { registerLazyLoadedDiagrams, detectType } from './diagram-api/detectType.js';
registerLazyLoadedDiagrams, import { loadRegisteredDiagrams } from './diagram-api/loadDiagram.js';
loadRegisteredDiagrams,
detectType,
} from './diagram-api/detectType.js';
import type { ParseErrorFunction } from './Diagram.js'; import type { ParseErrorFunction } from './Diagram.js';
import { isDetailedError } from './utils.js'; import { isDetailedError } from './utils.js';
import type { DetailedError } from './utils.js'; import type { DetailedError } from './utils.js';

View File

@ -32,6 +32,7 @@ import assignWithDepth from './assignWithDepth.js';
import { MermaidConfig } from './config.type.js'; import { MermaidConfig } from './config.type.js';
import memoize from 'lodash-es/memoize.js'; import memoize from 'lodash-es/memoize.js';
import merge from 'lodash-es/merge.js'; import merge from 'lodash-es/merge.js';
import { directiveRegex } from './diagram-api/regexes.js';
export const ZERO_WIDTH_SPACE = '\u200b'; export const ZERO_WIDTH_SPACE = '\u200b';
@ -58,7 +59,7 @@ const d3CurveTypes = {
curveStepAfter: curveStepAfter, curveStepAfter: curveStepAfter,
curveStepBefore: curveStepBefore, curveStepBefore: curveStepBefore,
}; };
const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
const directiveWithoutOpen = const directiveWithoutOpen =
/\s*(?:(\w+)(?=:):|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; /\s*(?:(\w+)(?=:):|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
@ -163,10 +164,10 @@ export const detectDirective = function (
); );
let match; let match;
const result = []; 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 // This is necessary to avoid infinite loops with zero-width matches
if (match.index === directive.lastIndex) { if (match.index === directiveRegex.lastIndex) {
directive.lastIndex++; directiveRegex.lastIndex++;
} }
if ( if (
(match && !type) || (match && !type) ||

6
pnpm-lock.yaml generated
View File

@ -434,6 +434,9 @@ importers:
jiti: jiti:
specifier: ^1.18.2 specifier: ^1.18.2
version: 1.18.2 version: 1.18.2
mermaid:
specifier: workspace:^
version: link:../..
vue: vue:
specifier: ^3.3 specifier: ^3.3
version: 3.3.4 version: 3.3.4
@ -486,6 +489,9 @@ importers:
jiti: jiti:
specifier: ^1.18.2 specifier: ^1.18.2
version: 1.18.2 version: 1.18.2
mermaid:
specifier: workspace:^
version: link:../..
vue: vue:
specifier: ^3.3 specifier: ^3.3
version: 3.3.4 version: 3.3.4