use the markdown-it Markdown parser by default (#1243)

The marked parser is deprecated in JSDoc 3.6.0 and will be removed in JSDoc 3.7.0.
This commit is contained in:
Jeff Williams 2017-07-22 19:00:24 -07:00
parent e6a313c4ca
commit 8191227607
3 changed files with 148 additions and 83 deletions

View File

@ -5,6 +5,10 @@
'use strict';
var env = require('jsdoc/env');
var logger = require('jsdoc/util/logger');
var MarkdownIt = require('markdown-it');
var marked = require('marked');
var mda = require('markdown-it-anchor');
var util = require('util');
/**
@ -13,29 +17,37 @@ var util = require('util');
*/
var parserNames = {
/**
* The "[markdown-js](https://github.com/evilstreak/markdown-js)" (aka "evilstreak") parser.
* The [`markdown-js`](https://github.com/evilstreak/markdown-js) (aka "evilstreak") parser.
*
* @deprecated Replaced by "marked," as markdown-js does not support inline HTML.
* @deprecated Replaced by `markdown-it`.
*/
evilstreak: 'marked',
evilstreak: 'markdownit',
/**
* The "GitHub-flavored Markdown" parser.
* @deprecated Replaced by "marked."
*
* @deprecated Replaced by `markdown-it`.
*/
gfm: 'marked',
gfm: 'markdownit',
/**
* The "[Marked](https://github.com/chjj/marked)" parser.
* The `markdown-it` parser.
*/
markdownit: 'markdownit',
/**
* The [Marked](https://github.com/chjj/marked) parser.
*
* @deprecated Will be replaced by `markdown-it` in JSDoc 3.7.0.
*/
marked: 'marked'
};
/**
* Escape underscores that occur within {@ ... } in order to protect them
* from the markdown parser(s).
* @param {String} source the source text to sanitize.
* @returns {String} `source` where underscores within {@ ... } have been
* protected with a preceding backslash (i.e. \_) -- the markdown parsers
* will strip the backslash and protect the underscore.
* Escape underscores that occur within an inline tag in order to protect them from the `marked`
* parser.
*
* @param {string} source - The source text to sanitize.
* @return {string} The source text, where underscores within inline tags have been protected with a
* preceding backslash (e.g., `\_`). The `marked` parser will strip the backslash and protect the
* underscore.
*/
function escapeUnderscores(source) {
return source.replace(/\{@[^}\r\n]+\}/g, function(wholeMatch) {
@ -63,6 +75,18 @@ function unescapeUrls(source) {
return source.replace(/(https?):\\\/\\\//g, '$1://');
}
/**
* Escape backslashes within inline tags so that they are not stripped.
*
* @param {string} source - The source text to escape.
* @return {string} The source text with backslashes escaped within inline tags.
*/
function escapeInlineTagBackslashes(source) {
return source.replace(/\{@[^}\r\n]+\}/g, function(wholeMatch) {
return wholeMatch.replace(/\\/g, '\\\\');
});
}
/**
* Escape characters in text within a code block.
*
@ -76,11 +100,25 @@ function escapeCode(source) {
}
/**
* Unencode quotes that occur within {@ ... } after the markdown parser has turned them
* into html entities (unfortunately it isn't possible to escape them before parsing)
* Wrap a code snippet in HTML tags that enable syntax highlighting.
*
* @param {string} code - The code snippet.
* @param {string?} language - The language of the code snippet.
* @return {string} The wrapped code snippet.
*/
function highlight(code, language) {
var langClass = language ? ' lang-' + language : '';
return util.format('<pre class="prettyprint source%s"><code>%s</code></pre>', langClass,
escapeCode(code));
}
/**
* Unencode quotes that occur within {@ ... } after the Markdown parser has turned them into HTML
* entities.
*
* @param {string} source - The source text to unencode.
* @return {string} The source text with html entity `&quot;` converted back to standard quotes
* @return {string} The source text with HTML entity `&quot;` converted back to standard quotes.
*/
function unencodeQuotes(source) {
return source.replace(/\{@[^}\r\n]+\}/g, function(wholeMatch) {
@ -88,7 +126,6 @@ function unencodeQuotes(source) {
});
}
/**
* Retrieve a function that accepts a single parameter containing Markdown source. The function uses
* the specified parser to transform the Markdown source to HTML, then returns the HTML as a string.
@ -100,60 +137,82 @@ function unencodeQuotes(source) {
* returns the resulting HTML.
*/
function getParseFunction(parserName, conf) {
var logger = require('jsdoc/util/logger');
var marked = require('marked');
var markedRenderer;
var parserFunction;
var renderer;
conf = conf || {};
if (parserName === parserNames.marked) {
if (conf.hardwrap) {
marked.setOptions({breaks: true});
}
switch (parserName) {
case parserNames.marked:
if (conf.hardwrap) {
marked.setOptions({breaks: true});
}
// Marked generates an "id" attribute for headers; this custom renderer suppresses it
markedRenderer = new marked.Renderer();
// Marked generates an "id" attribute for headers; this custom renderer suppresses it
renderer = new marked.Renderer();
if (!conf.idInHeadings) {
markedRenderer.heading = function(text, level) {
return util.format('<h%s>%s</h%s>', level, text, level);
if (!conf.idInHeadings) {
renderer.heading = function(text, level) {
return util.format('<h%s>%s</h%s>', level, text, level);
};
}
renderer.code = highlight;
parserFunction = function(source) {
var result;
source = escapeUnderscores(source);
source = escapeUrls(source);
result = marked(source, { renderer: renderer })
.replace(/\s+$/, '')
.replace(/&#39;/g, "'");
result = unescapeUrls(result);
result = unencodeQuotes(result);
return result;
};
}
parserFunction._parser = parserNames.marked;
// Allow prettyprint to work on inline code samples
markedRenderer.code = function(code, language) {
var langClass = language ? ' lang-' + language : '';
return parserFunction;
return util.format( '<pre class="prettyprint source%s"><code>%s</code></pre>',
langClass, escapeCode(code) );
};
case parserNames.markdownit:
renderer = new MarkdownIt({
breaks: Boolean(conf.hardwrap),
highlight: highlight,
html: true
});
parserFunction = function(source) {
var result;
if (conf.idInHeadings) {
renderer.use(mda);
}
source = escapeUnderscores(source);
source = escapeUrls(source);
parserFunction = function(source) {
var result;
result = marked(source, { renderer: markedRenderer })
.replace(/\s+$/, '')
.replace(/&#39;/g, "'");
source = escapeUrls(source);
source = escapeInlineTagBackslashes(source);
result = unescapeUrls(result);
result = unencodeQuotes(result);
result = renderer.render(source)
.replace(/\s+$/, '')
.replace(/&#39;/g, "'");
return result;
};
parserFunction._parser = parserNames.marked;
result = unescapeUrls(result);
result = unencodeQuotes(result);
return parserFunction;
}
else {
logger.error('Unrecognized Markdown parser "%s". Markdown support is disabled.',
parserName);
return result;
};
parserFunction._parser = parserNames.markdownit;
return undefined;
return parserFunction;
default:
logger.error('Unrecognized Markdown parser "%s". Markdown support is disabled.',
parserName);
return undefined;
}
}
@ -168,12 +227,7 @@ function getParseFunction(parserName, conf) {
*/
exports.getParser = function() {
var conf = env.conf.markdown;
var parser = (conf && conf.parser) ? parserNames[conf.parser] : parserNames.markdownit;
if (conf && conf.parser) {
return getParseFunction(parserNames[conf.parser], conf);
}
else {
// marked is the default parser
return getParseFunction(parserNames.marked, conf);
}
return getParseFunction(parser, conf);
};

View File

@ -19,6 +19,8 @@
"escape-string-regexp": "~1.0.5",
"js2xmlparser": "~3.0.0",
"klaw": "~2.0.0",
"markdown-it": "~8.3.1",
"markdown-it-anchor": "~4.0.0",
"marked": "~0.3.6",
"mkdirp": "~0.5.1",
"requizzle": "~0.2.1",

View File

@ -6,12 +6,12 @@ describe('jsdoc/util/markdown', function() {
it('should exist', function() {
expect(markdown).toBeDefined();
expect(typeof markdown).toEqual('object');
expect(typeof markdown).toBe('object');
});
it('should export a "getParser" function', function() {
expect(markdown.getParser).toBeDefined();
expect(typeof markdown.getParser).toEqual('function');
expect(typeof markdown.getParser).toBe('function');
});
describe('getParser', function() {
@ -30,19 +30,23 @@ describe('jsdoc/util/markdown', function() {
setMarkdownConf({});
parser = markdown.getParser();
expect(typeof parser).toEqual('function');
setMarkdownConf({parser: 'marked'});
parser = markdown.getParser();
expect(typeof parser).toEqual('function');
expect(typeof parser).toBe('function');
});
it('should use the marked parser when evilstreak is requested', function() {
it('should use the markdown-it parser by default', function() {
var parser;
setMarkdownConf({});
parser = markdown.getParser();
expect(parser._parser).toBe('markdownit');
});
it('should use the markdown-it parser when evilstreak is requested', function() {
var parser;
setMarkdownConf({parser: 'evilstreak'});
parser = markdown.getParser();
expect(parser._parser).toEqual('marked');
expect(parser._parser).toBe('markdownit');
});
it('should use the marked parser when requested', function() {
@ -50,15 +54,15 @@ describe('jsdoc/util/markdown', function() {
setMarkdownConf({parser: 'marked'});
parser = markdown.getParser();
expect(parser._parser).toEqual('marked');
expect(parser._parser).toBe('marked');
});
it('should use the marked parser when GFM is requested', function() {
it('should use the markdown-it parser when GFM is requested', function() {
var parser;
setMarkdownConf({parser: 'gfm'});
parser = markdown.getParser();
expect(parser._parser).toEqual('marked');
expect(parser._parser).toBe('markdownit');
});
it('should log an error if an unrecognized Markdown parser is requested', function() {
@ -70,14 +74,10 @@ describe('jsdoc/util/markdown', function() {
expect(logger.error).toHaveBeenCalled();
});
it('should not apply formatting to inline tags when the marked parser is enabled', function() {
var parser;
it('should not apply formatting to inline tags', function() {
var parser = markdown.getParser();
setMarkdownConf({parser: 'marked'});
parser = markdown.getParser();
// get the marked parser and do the test
expect(parser('{@link MyClass#_x} and {@link MyClass#_y}')).toEqual(
expect(parser('{@link MyClass#_x} and {@link MyClass#_y}')).toBe(
'<p>{@link MyClass#_x} and {@link MyClass#_y}</p>');
});
@ -98,7 +98,7 @@ describe('jsdoc/util/markdown', function() {
'```';
var convertedText = '' +
'<pre class="prettyprint source lang-html"><code>' +
'&lt;p>&lt;a href=&quot;#&quot;>Sample \'HTML.\'&lt;/a>&lt;/p>' +
'&lt;p>&lt;a href=&quot;#&quot;>Sample \'HTML.\'&lt;/a>&lt;/p>\n' +
'</code></pre>';
expect(parser(markdownText)).toBe(convertedText);
@ -110,8 +110,17 @@ describe('jsdoc/util/markdown', function() {
setMarkdownConf({hardwrap: true});
parser = markdown.getParser();
expect(parser('line one\nline two')).toEqual(
'<p>line one<br>line two</p>');
expect(parser('line one\nline two')).toBe(
'<p>line one<br>\nline two</p>');
});
it('should add heading IDs when idInHeadings is enabled', function() {
var parser;
setMarkdownConf({idInHeadings: true});
parser = markdown.getParser();
expect(parser('# Hello')).toBe('<h1 id="hello">Hello</h1>');
});
});
});