Skip to content

Commit

Permalink
Merge pull request #4359 from rhysd/bug/4358_suppress_error_rendering
Browse files Browse the repository at this point in the history
Add `suppressErrorRendering` option to avoid inserting 'Syntax error' message to DOM directly
  • Loading branch information
sidharthv96 authored Mar 23, 2024
2 parents 725b618 + ecfa149 commit 78587e1
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 21 deletions.
42 changes: 42 additions & 0 deletions cypress/integration/other/configuration.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
);
});
});
});
59 changes: 59 additions & 0 deletions cypress/platform/suppressError.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Mermaid Quick Test Page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
</head>
<body>
<div id="test">
<pre class="mermaid">
flowchart
a[This should be visible]
</pre
>
<pre class="mermaid">
flowchart
a --< b
</pre
>
<pre class="mermaid">
flowchart
a[This should be visible]
</pre
>
<pre class="mermaid">
---
config:
suppressErrorRendering: true # This should not affect anything, as suppressErrorRendering is a secure config
---
flowchart
a --< b
</pre
>
<pre class="mermaid">
---
config:
suppressErrorRendering: false # This should not affect anything, as suppressErrorRendering is a secure config
---
flowchart
a --< b
</pre
>
</div>
<script type="module">
import mermaid from './mermaid.esm.mjs';
const shouldSuppress =
new URLSearchParams(window.location.search).get('suppressErrorRendering') === 'true';
mermaid.initialize({ startOnLoad: false, suppressErrorRendering: shouldSuppress });
try {
await mermaid.run();
} catch {
if (window.Cypress) {
window.rendered = true;
}
}
</script>
</body>
</html>
3 changes: 2 additions & 1 deletion docs/config/setup/modules/mermaidAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const config = {
securityLevel: 'strict',
startOnLoad: true,
arrowMarkerAbsolute: false,
suppressErrorRendering: false,

er: {
diagramPadding: 20,
Expand Down Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions packages/mermaid/src/config.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
51 changes: 32 additions & 19 deletions packages/mermaid/src/mermaidAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function processAndSetConfigs(text: string) {
*/
async function parse(
text: string,
parseOptions: ParseOptions & { suppressErrors: true }
parseOptions: ParseOptions & { suppressErrors: true },
): Promise<ParseResult | false>;
async function parse(text: string, parseOptions?: ParseOptions): Promise<ParseResult>;
async function parse(text: string, parseOptions?: ParseOptions): Promise<ParseResult | false> {
Expand Down Expand Up @@ -138,7 +138,7 @@ async function parse(text: string, parseOptions?: ParseOptions): Promise<ParseRe
export const cssImportantStyles = (
cssClass: string,
element: string,
cssClasses: string[] = []
cssClasses: string[] = [],
): string => {
return `\n.${cssClass} ${element} { ${cssClasses.join(' !important; ')} !important; }`;
};
Expand All @@ -152,7 +152,7 @@ export const cssImportantStyles = (
*/
export const createCssStyles = (
config: MermaidConfig,
classDefs: Record<string, DiagramStyleClassDef> | null | undefined = {}
classDefs: Record<string, DiagramStyleClassDef> | null | undefined = {},
): string => {
let cssStyles = '';

Expand Down Expand Up @@ -201,7 +201,7 @@ export const createUserStyles = (
config: MermaidConfig,
graphType: string,
classDefs: Record<string, DiagramStyleClassDef> | undefined,
svgId: string
svgId: string,
): string => {
const userCSSstyles = createCssStyles(config, classDefs);
const allStyles = getStyles(graphType, userCSSstyles, config.themeVariables);
Expand All @@ -223,15 +223,15 @@ export const createUserStyles = (
export const cleanUpSvgCode = (
svgCode = '',
inSandboxMode: boolean,
useArrowMarkerUrls: boolean
useArrowMarkerUrls: boolean,
): string => {
let cleanedUpSvg = svgCode;

// Replace marker-end urls with just the # anchor (remove the preceding part of the URL)
if (!useArrowMarkerUrls && !inSandboxMode) {
cleanedUpSvg = cleanedUpSvg.replace(
/marker-end="url\([\d+./:=?A-Za-z-]*?#/g,
'marker-end="url(#'
'marker-end="url(#',
);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -347,7 +347,7 @@ export const removeExistingElements = (
const render = async function (
id: string,
text: string,
svgContainingElement?: Element
svgContainingElement?: Element,
): Promise<RenderResult> {
addDiagrams();

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

Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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'));
Expand All @@ -566,6 +578,7 @@ function addA11yInfo(
* securityLevel: 'strict',
* startOnLoad: true,
* arrowMarkerAbsolute: false,
* suppressErrorRendering: false,
*
* er: {
* diagramPadding: 20,
Expand Down
16 changes: 15 additions & 1 deletion packages/mermaid/src/schemas/config.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 78587e1

Please sign in to comment.