Skip to content

Commit

Permalink
Merge pull request #3004 from timmaffett/parse_parseError_fixes
Browse files Browse the repository at this point in the history
fix mermaidAPI.parse() behavior to match documentation, add tests to ensure behavior matches docs
  • Loading branch information
knsv committed May 10, 2022
2 parents 97cc8a4 + c8e5525 commit 734cef9
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 92 deletions.
4 changes: 2 additions & 2 deletions docs/newDiagram.md
Expand Up @@ -29,7 +29,7 @@ statement
In the extract of the grammar above, it is defined that a call to the setTitle method in the data object will be done when parsing and the title keyword is encountered.

```tip
Make sure that the `parseError` function for the parser is defined and calling `mermaidPAI.parseError`. This way a common way of detecting parse errors is provided for the end-user.
Make sure that the `parseError` function for the parser is defined and calling `mermaid.parseError`. This way a common way of detecting parse errors is provided for the end-user.
```

For more info look in the example diagram type:
Expand All @@ -38,7 +38,7 @@ The `yy` object has the following function:

```javascript
exports.parseError = function(err, hash){
mermaidAPI.parseError(err, hash)
mermaid.parseError(err, hash)
};
```

Expand Down
24 changes: 22 additions & 2 deletions src/mermaid.js
Expand Up @@ -188,18 +188,38 @@ if (typeof document !== 'undefined') {
);
}

/**
* ## setParseErrorHandler Alternativet to directly setting parseError using:
*
* ```js
* mermaid.parseError = function(err,hash){=
* forExampleDisplayErrorInGui(err); // do something with the error
* };
* ```
*
* This is provided for environments where the mermaid object can't directly have a new member added
* to it (eg. dart interop wrapper). (Initially there is no parseError member of mermaid).
*
* @param {function (err, hash)} newParseErrorHandler New parseError() callback.
*/
const setParseErrorHandler = function (newParseErrorHandler) {
mermaid.parseError = newParseErrorHandler;
};

const mermaid = {
startOnLoad: true,
htmlLabels: true,

mermaidAPI,
parse: mermaidAPI.parse,
render: mermaidAPI.render,
parse: mermaidAPI != undefined ? mermaidAPI.parse : null,
render: mermaidAPI != undefined ? mermaidAPI.render : null,

init,
initialize,

contentLoaded,

setParseErrorHandler,
};

export default mermaid;
193 changes: 107 additions & 86 deletions src/mermaidAPI.js
Expand Up @@ -63,99 +63,120 @@ import getStyles from './styles';
import theme from './themes';
import utils, { directiveSanitizer, assignWithDepth, sanitizeCss } from './utils';
import DOMPurify from 'dompurify';
import mermaid from './mermaid';

/**
* @param text
* @returns {any}
*/
function parse(text) {
text = text + '\n';
const cnf = configApi.getConfig();
const graphInit = utils.detectInit(text, cnf);
if (graphInit) {
reinitialize(graphInit);
log.info('reinit ', graphInit);
}
const graphType = utils.detectType(text, cnf);
let parser;

log.debug('Type ' + graphType);
switch (graphType) {
case 'gitGraph':
gitGraphAst.clear();
parser = gitGraphParser;
parser.parser.yy = gitGraphAst;
break;
case 'flowchart':
flowDb.clear();
parser = flowParser;
parser.parser.yy = flowDb;
break;
case 'flowchart-v2':
flowDb.clear();
parser = flowParser;
parser.parser.yy = flowDb;
break;
case 'sequence':
sequenceDb.clear();
parser = sequenceParser;
parser.parser.yy = sequenceDb;
break;
case 'gantt':
parser = ganttParser;
parser.parser.yy = ganttDb;
break;
case 'class':
parser = classParser;
parser.parser.yy = classDb;
break;
case 'classDiagram':
parser = classParser;
parser.parser.yy = classDb;
break;
case 'state':
parser = stateParser;
parser.parser.yy = stateDb;
break;
case 'stateDiagram':
parser = stateParser;
parser.parser.yy = stateDb;
break;
case 'info':
log.debug('info info info');
parser = infoParser;
parser.parser.yy = infoDb;
break;
case 'pie':
log.debug('pie');
parser = pieParser;
parser.parser.yy = pieDb;
break;
case 'er':
log.debug('er');
parser = erParser;
parser.parser.yy = erDb;
break;
case 'journey':
log.debug('Journey');
parser = journeyParser;
parser.parser.yy = journeyDb;
break;
case 'requirement':
case 'requirementDiagram':
log.debug('RequirementDiagram');
parser = requirementParser;
parser.parser.yy = requirementDb;
break;
var parseEncounteredException = false;
try {
text = text + '\n';
const cnf = configApi.getConfig();
const graphInit = utils.detectInit(text, cnf);
if (graphInit) {
reinitialize(graphInit);
log.info('reinit ', graphInit);
}
const graphType = utils.detectType(text, cnf);
let parser;

log.debug('Type ' + graphType);
switch (graphType) {
case 'gitGraph':
gitGraphAst.clear();
parser = gitGraphParser;
parser.parser.yy = gitGraphAst;
break;
case 'flowchart':
flowDb.clear();
parser = flowParser;
parser.parser.yy = flowDb;
break;
case 'flowchart-v2':
flowDb.clear();
parser = flowParser;
parser.parser.yy = flowDb;
break;
case 'sequence':
sequenceDb.clear();
parser = sequenceParser;
parser.parser.yy = sequenceDb;
break;
case 'gantt':
parser = ganttParser;
parser.parser.yy = ganttDb;
break;
case 'class':
parser = classParser;
parser.parser.yy = classDb;
break;
case 'classDiagram':
parser = classParser;
parser.parser.yy = classDb;
break;
case 'state':
parser = stateParser;
parser.parser.yy = stateDb;
break;
case 'stateDiagram':
parser = stateParser;
parser.parser.yy = stateDb;
break;
case 'info':
log.debug('info info info');
parser = infoParser;
parser.parser.yy = infoDb;
break;
case 'pie':
log.debug('pie');
parser = pieParser;
parser.parser.yy = pieDb;
break;
case 'er':
log.debug('er');
parser = erParser;
parser.parser.yy = erDb;
break;
case 'journey':
log.debug('Journey');
parser = journeyParser;
parser.parser.yy = journeyDb;
break;
case 'requirement':
case 'requirementDiagram':
log.debug('RequirementDiagram');
parser = requirementParser;
parser.parser.yy = requirementDb;
break;
}
parser.parser.yy.graphType = graphType;
parser.parser.yy.parseError = (str, hash) => {
const error = { str, hash };
throw error;
};

parser.parse(text);
} catch (error) {
parseEncounteredException = true;
// Is this the correct way to access mermiad's parseError()
// method ? (or global.mermaid.parseError()) ?
if (mermaid.parseError) {
if (error.str != undefined) {
// handle case where error string and hash were
// wrapped in object like`const error = { str, hash };`
mermaid.parseError(error.str, error.hash);
} else {
// assume it is just error string and pass it on
mermaid.parseError(error);
}
} else {
// No mermaid.parseError() handler defined, so re-throw it
throw error;
}
}
parser.parser.yy.graphType = graphType;
parser.parser.yy.parseError = (str, hash) => {
const error = { str, hash };
throw error;
};

parser.parse(text);
return parser;
return !parseEncounteredException;
}

export const encodeEntities = function (text) {
Expand Down
21 changes: 19 additions & 2 deletions src/mermaidAPI.spec.js
@@ -1,3 +1,4 @@
import mermaid from './mermaid';
import mermaidAPI from './mermaidAPI';
import { assignWithDepth } from './utils';

Expand Down Expand Up @@ -119,12 +120,28 @@ describe('when using mermaidAPI and ', function () {
expect(mermaidAPI.getConfig().dompurifyConfig.ADD_ATTR).toEqual(['onclick']);
});
});
describe('checking validity of input ', function () {
it('it should throw for an invalid definiton', function () {
describe('test mermaidApi.parse() for checking validity of input ', function () {
mermaid.parseError = undefined; // ensure it parseError undefined
it('it should throw for an invalid definiton (with no mermaid.parseError() defined)', function () {
expect(mermaid.parseError).toEqual(undefined);
expect(() => mermaidAPI.parse('this is not a mermaid diagram definition')).toThrow();
});
it('it should not throw for a valid definiton', function () {
expect(() => mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).not.toThrow();
});
it('it should return false for invalid definiton WITH a parseError() callback defined', function () {
var parseErrorWasCalled = false;
// also test setParseErrorHandler() call working to set mermaid.parseError
mermaid.setParseErrorHandler(function (error, hash) {
// got here.
parseErrorWasCalled = true;
});
expect(mermaid.parseError).not.toEqual(undefined);
expect(mermaidAPI.parse('this is not a mermaid diagram definition')).toEqual(false);
expect(parseErrorWasCalled).toEqual(true);
});
it('it should return true for valid definiton', function () {
expect(mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).toEqual(true);
});
});
});

0 comments on commit 734cef9

Please sign in to comment.