123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- /**
- * Provides access to Markdown-related functions.
- * @module jsdoc/util/markdown
- */
- const env = require('jsdoc/env');
- const logger = require('jsdoc/util/logger');
- const MarkdownIt = require('markdown-it');
- const { marked } = require('marked');
- const mda = require('markdown-it-anchor');
- const path = require('jsdoc/path');
- const util = require('util');
- /**
- * Enumeration of Markdown parsers that are available.
- * @enum {String}
- */
- const parserNames = {
- /**
- * The [`markdown-js`](https://github.com/evilstreak/markdown-js) (aka "evilstreak") parser.
- *
- * @deprecated Replaced by `markdown-it`.
- */
- evilstreak: 'markdownit',
- /**
- * The "GitHub-flavored Markdown" parser.
- *
- * @deprecated Replaced by `markdown-it`.
- */
- gfm: 'markdownit',
- /**
- * 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 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, wholeMatch => wholeMatch.replace(/(^|[^\\])_/g, '$1\\_'));
- }
- /**
- * Escape HTTP/HTTPS URLs so that they are not automatically converted to HTML links.
- *
- * @param {string} source - The source text to escape.
- * @return {string} The source text with escape characters added to HTTP/HTTPS URLs.
- */
- function escapeUrls(source) {
- return source.replace(/(https?):\/\//g, '$1:\\/\\/');
- }
- /**
- * Unescape HTTP/HTTPS URLs after Markdown parsing is complete.
- *
- * @param {string} source - The source text to unescape.
- * @return {string} The source text with escape characters removed from HTTP/HTTPS URLs.
- */
- 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, wholeMatch => wholeMatch.replace(/\\/g, '\\\\'));
- }
- /**
- * Escape characters in text within a code block.
- *
- * @param {string} source - The source text to escape.
- * @return {string} The escaped source text.
- */
- function escapeCode(source) {
- return source.replace(/</g, '<')
- .replace(/"/g, '"')
- .replace(/'/g, ''');
- }
- /**
- * 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) {
- let classString;
- let langClass = '';
- if (language && (language !== 'plain')) {
- langClass = ` lang-${language}`;
- }
- if (language !== 'plain') {
- classString = util.format(' class="prettyprint source%s"', langClass);
- }
- else {
- classString = ' class="source"';
- }
- return util.format('<pre%s><code>%s</code></pre>', classString, 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 `"` converted back to standard quotes.
- */
- function unencodeQuotes(source) {
- return source.replace(/\{@[^}\r\n]+\}/g, wholeMatch => wholeMatch.replace(/"/g, '"'));
- }
- /**
- * Get the appropriate function for applying syntax highlighting to text, based on the user's
- * Markdown configuration settings.
- *
- * @param {Object} conf - The user's Markdown configuration settings.
- * @return {function} The highlighter function.
- */
- function getHighlighter(conf) {
- let highlighter;
- let highlighterPath;
- switch (typeof conf.highlight) {
- case 'string':
- highlighterPath = path.getResourcePath(conf.highlight);
- if (highlighterPath) {
- highlighter = require(highlighterPath).highlight;
- if (typeof highlighter !== 'function') {
- logger.error('The syntax highlighting module "%s" does not assign a method ' +
- 'to exports.highlight. Using the default syntax highlighter.',
- conf.highlight);
- highlighter = highlight;
- }
- }
- else {
- logger.error('Unable to find the syntax highlighting module "%s". Using the ' +
- 'default syntax highlighter.', conf.highlight);
- highlighter = highlight;
- }
- break;
- case 'function':
- highlighter = conf.highlight;
- break;
- default:
- highlighter = highlight;
- }
- return highlighter;
- }
- /**
- * 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.
- *
- * @private
- * @param {String} parserName The name of the selected parser.
- * @param {Object} [conf] Configuration for the selected parser, if any.
- * @returns {Function} A function that accepts Markdown source, feeds it to the selected parser, and
- * returns the resulting HTML.
- */
- function getParseFunction(parserName, conf) {
- let highlighter;
- let parserFunction;
- let renderer;
- conf = conf || {};
- highlighter = getHighlighter(conf);
- switch (parserName) {
- case parserNames.marked:
- if (conf.hardwrap) {
- marked.setOptions({breaks: true});
- }
- // Marked generates an "id" attribute for headers; this custom renderer suppresses it
- renderer = new marked.Renderer();
- if (!conf.idInHeadings) {
- renderer.heading = (text, level) => util.format('<h%s>%s</h%s>', level, text, level);
- }
- renderer.code = highlighter;
- parserFunction = source => {
- let result;
- source = escapeUnderscores(source);
- source = escapeUrls(source);
- result = marked(source, { renderer: renderer })
- .replace(/\s+$/, '')
- .replace(/'/g, "'");
- result = unescapeUrls(result);
- result = unencodeQuotes(result);
- return result;
- };
- parserFunction._parser = parserNames.marked;
- return parserFunction;
- case parserNames.markdownit:
- renderer = new MarkdownIt({
- breaks: Boolean(conf.hardwrap),
- highlight: highlighter,
- html: true
- });
- if (conf.idInHeadings) {
- renderer.use(mda, { tabIndex: false });
- }
- parserFunction = source => {
- let result;
- source = escapeUrls(source);
- source = escapeInlineTagBackslashes(source);
- result = renderer.render(source)
- .replace(/\s+$/, '')
- .replace(/'/g, "'");
- result = unescapeUrls(result);
- result = unencodeQuotes(result);
- return result;
- };
- parserFunction._parser = parserNames.markdownit;
- return parserFunction;
- default:
- logger.error('Unrecognized Markdown parser "%s". Markdown support is disabled.',
- parserName);
- return undefined;
- }
- }
- /**
- * Retrieve a Markdown parsing function based on the value of the `conf.json` file's
- * `env.conf.markdown` property. The parsing function accepts a single parameter containing Markdown
- * source. The function uses the parser specified in `conf.json` to transform the Markdown source to
- * HTML, then returns the HTML as a string.
- *
- * @returns {function} A function that accepts Markdown source, feeds it to the selected parser, and
- * returns the resulting HTML.
- */
- exports.getParser = () => {
- const conf = env.conf.markdown;
- const parser = (conf && conf.parser) ? parserNames[conf.parser] : parserNames.markdownit;
- return getParseFunction(parser, conf);
- };
|