diff --git a/cypress/integration/other/configuration.spec.js b/cypress/integration/other/configuration.spec.js
index 23338271f5..9fd0815676 100644
--- a/cypress/integration/other/configuration.spec.js
+++ b/cypress/integration/other/configuration.spec.js
@@ -125,4 +125,46 @@ describe('Configuration', () => {
);
});
});
+
+ describe('suppressErrorRendering', () => {
+ beforeEach(() => {
+ cy.on('uncaught:exception', (err, runnable) => {
+ return !err.message.includes('Parse error on line');
+ });
+ });
+
+ it('should not render error diagram if suppressErrorRendering is set', () => {
+ const url = 'http://localhost:9000/suppressError.html?suppressErrorRendering=true';
+ cy.visit(url);
+ cy.window().should('have.property', 'rendered', true);
+ cy.get('#test')
+ .find('svg')
+ .should(($svg) => {
+ // all failing diagrams should not appear!
+ expect($svg).to.have.length(2);
+ // none of the diagrams should be error diagrams
+ expect($svg).to.not.contain('Syntax error');
+ });
+ cy.matchImageSnapshot(
+ 'configuration.spec-should-not-render-error-diagram-if-suppressErrorRendering-is-set'
+ );
+ });
+
+ it('should render error diagram if suppressErrorRendering is not set', () => {
+ const url = 'http://localhost:9000/suppressError.html';
+ cy.visit(url);
+ cy.window().should('have.property', 'rendered', true);
+ cy.get('#test')
+ .find('svg')
+ .should(($svg) => {
+ // all five diagrams should be rendered
+ expect($svg).to.have.length(5);
+ // some of the diagrams should be error diagrams
+ expect($svg).to.contain('Syntax error');
+ });
+ cy.matchImageSnapshot(
+ 'configuration.spec-should-render-error-diagram-if-suppressErrorRendering-is-not-set'
+ );
+ });
+ });
});
diff --git a/cypress/platform/suppressError.html b/cypress/platform/suppressError.html
new file mode 100644
index 0000000000..347a82c79c
--- /dev/null
+++ b/cypress/platform/suppressError.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+ Mermaid Quick Test Page
+
+
+
+
+
+ flowchart
+ a[This should be visible]
+
+
+ flowchart
+ a --< b
+
+
+ flowchart
+ a[This should be visible]
+
+
+ ---
+ config:
+ suppressErrorRendering: true # This should not affect anything, as suppressErrorRendering is a secure config
+ ---
+ flowchart
+ a --< b
+
+
+ ---
+ config:
+ suppressErrorRendering: false # This should not affect anything, as suppressErrorRendering is a secure config
+ ---
+ flowchart
+ a --< b
+
+
+
+
+
diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md
index b078a688fb..1a68b05bd0 100644
--- a/docs/config/setup/modules/mermaidAPI.md
+++ b/docs/config/setup/modules/mermaidAPI.md
@@ -43,6 +43,7 @@ const config = {
securityLevel: 'strict',
startOnLoad: true,
arrowMarkerAbsolute: false,
+ suppressErrorRendering: false,
er: {
diagramPadding: 20,
@@ -97,7 +98,7 @@ mermaid.initialize(config);
#### Defined in
-[mermaidAPI.ts:622](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L622)
+[mermaidAPI.ts:635](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L635)
## Functions
diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts
index 97ed4eb8dc..2ff19c2d6e 100644
--- a/packages/mermaid/src/config.type.ts
+++ b/packages/mermaid/src/config.type.ts
@@ -159,6 +159,12 @@ export interface MermaidConfig {
dompurifyConfig?: DOMPurifyConfiguration;
wrap?: boolean;
fontSize?: number;
+ /**
+ * Suppresses inserting 'Syntax error' diagram in the DOM.
+ * This is useful when you want to control how to handle syntax errors in your application.
+ *
+ */
+ suppressErrorRendering?: boolean;
}
/**
* The object containing configurations specific for packet diagrams.
diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts
index a3eee5d047..f394ef7f1d 100644
--- a/packages/mermaid/src/mermaidAPI.ts
+++ b/packages/mermaid/src/mermaidAPI.ts
@@ -110,7 +110,7 @@ function processAndSetConfigs(text: string) {
*/
async function parse(
text: string,
- parseOptions: ParseOptions & { suppressErrors: true }
+ parseOptions: ParseOptions & { suppressErrors: true },
): Promise;
async function parse(text: string, parseOptions?: ParseOptions): Promise;
async function parse(text: string, parseOptions?: ParseOptions): Promise {
@@ -138,7 +138,7 @@ async function parse(text: string, parseOptions?: ParseOptions): Promise {
return `\n.${cssClass} ${element} { ${cssClasses.join(' !important; ')} !important; }`;
};
@@ -152,7 +152,7 @@ export const cssImportantStyles = (
*/
export const createCssStyles = (
config: MermaidConfig,
- classDefs: Record | null | undefined = {}
+ classDefs: Record | null | undefined = {},
): string => {
let cssStyles = '';
@@ -201,7 +201,7 @@ export const createUserStyles = (
config: MermaidConfig,
graphType: string,
classDefs: Record | undefined,
- svgId: string
+ svgId: string,
): string => {
const userCSSstyles = createCssStyles(config, classDefs);
const allStyles = getStyles(graphType, userCSSstyles, config.themeVariables);
@@ -223,7 +223,7 @@ export const createUserStyles = (
export const cleanUpSvgCode = (
svgCode = '',
inSandboxMode: boolean,
- useArrowMarkerUrls: boolean
+ useArrowMarkerUrls: boolean,
): string => {
let cleanedUpSvg = svgCode;
@@ -231,7 +231,7 @@ export const cleanUpSvgCode = (
if (!useArrowMarkerUrls && !inSandboxMode) {
cleanedUpSvg = cleanedUpSvg.replace(
/marker-end="url\([\d+./:=?A-Za-z-]*?#/g,
- 'marker-end="url(#'
+ 'marker-end="url(#',
);
}
@@ -279,7 +279,7 @@ export const appendDivSvgG = (
id: string,
enclosingDivId: string,
divStyle?: string,
- svgXlink?: string
+ svgXlink?: string,
): D3Element => {
const enclosingDiv = parentRoot.append('div');
enclosingDiv.attr('id', enclosingDivId);
@@ -328,7 +328,7 @@ export const removeExistingElements = (
doc: Document,
id: string,
divId: string,
- iFrameId: string
+ iFrameId: string,
) => {
// Remove existing SVG element if it exists
doc.getElementById(id)?.remove();
@@ -347,7 +347,7 @@ export const removeExistingElements = (
const render = async function (
id: string,
text: string,
- svgContainingElement?: Element
+ svgContainingElement?: Element,
): Promise {
addDiagrams();
@@ -368,6 +368,16 @@ const render = async function (
const enclosingDivID = 'd' + id;
const enclosingDivID_selector = '#' + enclosingDivID;
+ const removeTempElements = () => {
+ // -------------------------------------------------------------------------------
+ // Remove the temporary HTML element if appropriate
+ const tmpElementSelector = isSandboxed ? iFrameID_selector : enclosingDivID_selector;
+ const node = select(tmpElementSelector).node();
+ if (node && 'remove' in node) {
+ node.remove();
+ }
+ };
+
let root: any = select('body');
const isSandboxed = config.securityLevel === SECURITY_LVL_SANDBOX;
@@ -424,6 +434,10 @@ const render = async function (
try {
diag = await Diagram.fromText(text, { title: processed.title });
} catch (error) {
+ if (config.suppressErrorRendering) {
+ removeTempElements();
+ throw error;
+ }
diag = await Diagram.fromText('error');
parseEncounteredException = error;
}
@@ -451,7 +465,11 @@ const render = async function (
try {
await diag.renderer.draw(text, id, version, diag);
} catch (e) {
- errorRenderer.draw(text, id, version);
+ if (config.suppressErrorRendering) {
+ removeTempElements();
+ } else {
+ errorRenderer.draw(text, id, version);
+ }
throw e;
}
@@ -487,13 +505,7 @@ const render = async function (
throw parseEncounteredException;
}
- // -------------------------------------------------------------------------------
- // Remove the temporary HTML element if appropriate
- const tmpElementSelector = isSandboxed ? iFrameID_selector : enclosingDivID_selector;
- const node = select(tmpElementSelector).node();
- if (node && 'remove' in node) {
- node.remove();
- }
+ removeTempElements();
return {
diagramType,
@@ -520,7 +532,7 @@ function initialize(options: MermaidConfig = {}) {
if (options?.theme && options.theme in theme) {
// Todo merge with user options
options.themeVariables = theme[options.theme as keyof typeof theme].getThemeVariables(
- options.themeVariables
+ options.themeVariables,
);
} else if (options) {
options.themeVariables = theme.default.getThemeVariables(options.themeVariables);
@@ -550,7 +562,7 @@ function addA11yInfo(
diagramType: string,
svgNode: D3Element,
a11yTitle?: string,
- a11yDescr?: string
+ a11yDescr?: string,
): void {
setA11yDiagramInfo(svgNode, diagramType);
addSVGa11yTitleDescription(svgNode, a11yTitle, a11yDescr, svgNode.attr('id'));
@@ -566,6 +578,7 @@ function addA11yInfo(
* securityLevel: 'strict',
* startOnLoad: true,
* arrowMarkerAbsolute: false,
+ * suppressErrorRendering: false,
*
* er: {
* diagramPadding: 20,
diff --git a/packages/mermaid/src/schemas/config.schema.yaml b/packages/mermaid/src/schemas/config.schema.yaml
index d6d084f8c7..d580300744 100644
--- a/packages/mermaid/src/schemas/config.schema.yaml
+++ b/packages/mermaid/src/schemas/config.schema.yaml
@@ -159,7 +159,15 @@ properties:
in the current `currentConfig`.
This prevents malicious graph directives from overriding a site's default security.
- default: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize', 'maxEdges']
+ default:
+ [
+ 'secure',
+ 'securityLevel',
+ 'startOnLoad',
+ 'maxTextSize',
+ 'suppressErrorRendering',
+ 'maxEdges',
+ ]
type: array
items:
type: string
@@ -235,6 +243,12 @@ properties:
fontSize:
type: number
default: 16
+ suppressErrorRendering:
+ type: boolean
+ default: false
+ description: |
+ Suppresses inserting 'Syntax error' diagram in the DOM.
+ This is useful when you want to control how to handle syntax errors in your application.
$defs: # JSON Schema definition (maybe we should move these to a separate file)
BaseDiagramConfig: