Skip to content

Commit

Permalink
Merge pull request #6002 from aloisklink/fix/make-parse-error-on-inva…
Browse files Browse the repository at this point in the history
…lid-shape

fix: make `mermaid.parse` throw an error on invalid shapes
  • Loading branch information
knsv authored Oct 29, 2024
2 parents bdf145f + f6c32b6 commit 8d0902d
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/thick-elephants-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mermaid': patch
---

fix: error `mermaid.parse` on an invalid shape, so that it matches the errors thrown by `mermaid.render`
47 changes: 30 additions & 17 deletions packages/mermaid/src/diagrams/flowchart/flowDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { select } from 'd3';
import utils, { getEdgeId } from '../../utils.js';
import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js';
import common from '../common/common.js';
import { isValidShape, type ShapeID } from '../../rendering-util/rendering-elements/shapes.js';
import type { Node, Edge } from '../../rendering-util/types.js';
import { log } from '../../logger.js';
import * as yaml from 'js-yaml';
Expand All @@ -14,7 +15,15 @@ import {
setDiagramTitle,
getDiagramTitle,
} from '../common/commonDb.js';
import type { FlowVertex, FlowClass, FlowSubGraph, FlowText, FlowEdge, FlowLink } from './types.js';
import type {
FlowVertex,
FlowClass,
FlowSubGraph,
FlowText,
FlowEdge,
FlowLink,
FlowVertexTypeParam,
} from './types.js';
import type { NodeMetaData } from '../../types.js';

const MERMAID_DOM_ID_PREFIX = 'flowchart-';
Expand Down Expand Up @@ -53,12 +62,11 @@ export const lookUpDomId = function (id: string) {

/**
* Function called by parser when a node definition has been found
*
*/
export const addVertex = function (
id: string,
textObj: FlowText,
type: 'group',
type: FlowVertexTypeParam,
style: string[],
classes: string[],
dir: string,
Expand Down Expand Up @@ -133,14 +141,15 @@ export const addVertex = function (
}
// console.log('yamlData', yamlData);
const doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData;
if (doc.shape && (doc.shape !== doc.shape.toLowerCase() || doc.shape.includes('_'))) {
throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`);
}

// console.log('yamlData doc', doc);
if (doc?.shape) {
if (doc.shape) {
if (doc.shape !== doc.shape.toLowerCase() || doc.shape.includes('_')) {
throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`);
} else if (!isValidShape(doc.shape)) {
throw new Error(`No such shape: ${doc.shape}.`);
}
vertex.type = doc?.shape;
}

if (doc?.label) {
vertex.text = doc?.label;
}
Expand Down Expand Up @@ -816,7 +825,7 @@ export const lex = {
firstGraph,
};

const getTypeFromVertex = (vertex: FlowVertex) => {
const getTypeFromVertex = (vertex: FlowVertex): ShapeID => {
if (vertex.img) {
return 'imageSquare';
}
Expand All @@ -832,14 +841,18 @@ const getTypeFromVertex = (vertex: FlowVertex) => {
}
return 'icon';
}
if (vertex.type === 'square') {
return 'squareRect';
}
if (vertex.type === 'round') {
return 'roundedRect';
switch (vertex.type) {
case 'square':
case undefined:
return 'squareRect';
case 'round':
return 'roundedRect';
case 'ellipse':
// @ts-expect-error -- Ellipses are broken, see https://github.com/mermaid-js/mermaid/issues/5976
return 'ellipse';
default:
return vertex.type;
}

return vertex.type ?? 'squareRect';
};

const findNode = (nodes: Node[], id: string) => nodes.find((node) => node.id === id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,21 @@ describe('when parsing directions', function () {
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is }');
});
it('should error on non-existent shape', function () {
expect(() => {
flow.parser.parse(`flowchart TB
A@{ shape: this-shape-does-not-exist }
`);
}).toThrow('No such shape: this-shape-does-not-exist.');
});
it('should error on internal-only shape', function () {
expect(() => {
// this shape does exist, but it's only supposed to be for internal/backwards compatibility use
flow.parser.parse(`flowchart TB
A@{ shape: rect_left_inv_arrow }
`);
}).toThrow('No such shape: rect_left_inv_arrow. Shape names should be lowercase.');
});
it('Diamond shapes should work as usual', function () {
const res = flow.parser.parse(`flowchart TB
A{This is a label}
Expand Down
27 changes: 26 additions & 1 deletion packages/mermaid/src/diagrams/flowchart/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
import type { ShapeID } from '../../rendering-util/rendering-elements/shapes.js';

/**
* Valid `type` args to `yy.addVertex` taken from
* `packages/mermaid/src/diagrams/flowchart/parser/flow.jison`
*/
export type FlowVertexTypeParam =
| undefined
| 'square'
| 'doublecircle'
| 'circle'
| 'ellipse'
| 'stadium'
| 'subroutine'
| 'rect'
| 'cylinder'
| 'round'
| 'diamond'
| 'hexagon'
| 'odd'
| 'trapezoid'
| 'inv_trapezoid'
| 'lean_right'
| 'lean_left';

export interface FlowVertex {
classes: string[];
dir?: string;
Expand All @@ -10,7 +35,7 @@ export interface FlowVertex {
props?: any;
styles: string[];
text?: string;
type?: string;
type?: ShapeID | FlowVertexTypeParam;
icon?: string;
form?: string;
pos?: 't' | 'b';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function insertNode(elem: SVGGroup, node: Node, renderOptions: Shap
}
}

const shapeHandler = shapes[(node.shape ?? 'undefined') as keyof typeof shapes];
const shapeHandler = node.shape ? shapes[node.shape] : undefined;

if (!shapeHandler) {
throw new Error(`No such shape: ${node.shape}. Please check your syntax.`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,4 +500,8 @@ const generateShapeMap = () => {

export const shapes = generateShapeMap();

export function isValidShape(shape: string): shape is ShapeID {
return shape in shapes;
}

export type ShapeID = keyof typeof shapes;
3 changes: 2 additions & 1 deletion packages/mermaid/src/rendering-util/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type MarkdownWordType = 'normal' | 'strong' | 'em';
import type { MermaidConfig } from '../config.type.js';
import type { ShapeID } from './rendering-elements/shapes.js';
export interface MarkdownWord {
content: string;
type: MarkdownWordType;
Expand Down Expand Up @@ -37,7 +38,7 @@ export interface Node {
linkTarget?: string;
tooltip?: string;
padding?: number; //REMOVE?, use from LayoutData.config - Keep, this could be shape specific
shape?: string;
shape?: ShapeID;
isGroup: boolean;
width?: number;
height?: number;
Expand Down

0 comments on commit 8d0902d

Please sign in to comment.