Skip to the code
cf.

Inline Markdown to HTML

Write markdownToHtml(text). It converts four of Markdown's inline styles, code, bold, italic, and links, into HTML, and returns the HTML as a string. Everything else is text, including line breaks and characters like # and -.

Text must stay text. In text, code, and URLs, replace &, <, >, ", and ' with &amp;, &lt;, &gt;, &quot;, and &#39;, so the only tags in the result are the ones described here.

Read text from left to right. When you reach a marker, look for its closing marker: the first one after it. If there is one, the pair becomes an element, and reading continues after the closing marker. If there isn't, the marker is ordinary text.

Text between a backtick and the next backtick becomes <code>…</code>. Nothing inside code is styled: stars and brackets in it stay as they are.

** is closed by the next ** and becomes <strong>…</strong>. A * that isn't part of a ** is closed by the next such * and becomes <em>…</em>. Bold and italic can contain the other styles: **a *b* c** gives <strong>a <em>b</em> c</strong>.

[ starts a link when the first ] after it is followed right away by (, and a ) comes after that. The link text, between [ and ], can contain code, bold, and italic. The URL is everything between ( and the first ): [the *docs*](/docs) gives <a href="/docs">the <em>docs</em></a>.

A link is only made when its URL starts with https://, http://, mailto:, /, or #, ignoring case. For any other URL, such as javascript:alert(1), output the link text on its own, with no <a> and no URL.

markdownToHtml("Use **bold**, *italic*, and `code`.");
// "Use <strong>bold</strong>, <em>italic</em>, and <code>code</code>."
markdownToHtml("See [the *docs*](https://example.com) & <b>more</b>");
// 'See <a href="https://example.com">the <em>docs</em></a> &amp; &lt;b&gt;more&lt;/b&gt;'
markdownToHtml("Run `**x**` now"); // "Run <code>**x**</code> now"
markdownToHtml("[win a prize](javascript:steal)"); // "win a prize"
markdownToHtml("2 ** 3, and a lone `");            // "2 ** 3, and a lone `"

What the tests check

  1. escapes text, so the only tags are the ones it adds
    expect(markdownToHtml("Tom & Jerry <3")).toBe("Tom &amp; Jerry &lt;3");
    expect(markdownToHtml('<img src=x onerror="alert(1)">')).toBe("&lt;img src=x onerror=&quot;alert(1)&quot;&gt;");
    expect(markdownToHtml("It's plain # text - really")).toBe("It&#39;s plain # text - really");
    
  2. turns text between backticks into code, unstyled
    expect(markdownToHtml("Run `npm test` now")).toBe("Run <code>npm test</code> now");
    expect(markdownToHtml("`**not bold**` and `[a](/b)`")).toBe("<code>**not bold**</code> and <code>[a](/b)</code>");
    expect(markdownToHtml('`<div class="x">`')).toBe("<code>&lt;div class=&quot;x&quot;&gt;</code>");
    
  3. turns ** into strong and * into em
    expect(markdownToHtml("**bold** and *italic*")).toBe("<strong>bold</strong> and <em>italic</em>");
    expect(markdownToHtml("**a** and **b**")).toBe("<strong>a</strong> and <strong>b</strong>");
    expect(markdownToHtml("un*frigging*believable")).toBe("un<em>frigging</em>believable");
    
  4. styles text inside bold, italic, and links
    expect(markdownToHtml("**a *b* c**")).toBe("<strong>a <em>b</em> c</strong>");
    expect(markdownToHtml("*a **b** c*")).toBe("<em>a <strong>b</strong> c</em>");
    expect(markdownToHtml("**see `x` & [docs](/d)**")).toBe('<strong>see <code>x</code> &amp; <a href="/d">docs</a></strong>');
    expect(markdownToHtml("[**Docs** for `npm`](/docs)")).toBe('<a href="/docs"><strong>Docs</strong> for <code>npm</code></a>');
    
  5. turns [text](url) into a link
    expect(markdownToHtml("See [the docs](https://example.com/docs).")).toBe('See <a href="https://example.com/docs">the docs</a>.');
    expect(markdownToHtml("[a](/x) or [b](#top)")).toBe('<a href="/x">a</a> or <a href="#top">b</a>');
    expect(markdownToHtml("[mail me](mailto:ada@example.com)")).toBe('<a href="mailto:ada@example.com">mail me</a>');
    expect(markdownToHtml("[old](http://example.com)")).toBe('<a href="http://example.com">old</a>');
    
  6. escapes link text and URLs
    expect(markdownToHtml('[<b>](/search?q=a&b="c")')).toBe('<a href="/search?q=a&amp;b=&quot;c&quot;">&lt;b&gt;</a>');
    expect(markdownToHtml('[x](/" onclick="alert(1))')).toBe('<a href="/&quot; onclick=&quot;alert(1">x</a>)');
    
  7. makes links only to safe URLs
    expect(markdownToHtml("[click](javascript:void 0)")).toBe("click");
    expect(markdownToHtml("[click](JavaScript:void 0)")).toBe("click");
    expect(markdownToHtml("[pic](data:text/html,<script>x</script>)")).toBe("pic");
    expect(markdownToHtml("[**win**]( javascript:void 0)")).toBe("<strong>win</strong>");
    expect(markdownToHtml("[ok](HTTPS://EXAMPLE.COM)")).toBe('<a href="HTTPS://EXAMPLE.COM">ok</a>');
    
  8. leaves a marker without a closing marker as text
    expect(markdownToHtml("2 * 3 = 6")).toBe("2 * 3 = 6");
    expect(markdownToHtml("a ** b *c*")).toBe("a ** b <em>c</em>");
    expect(markdownToHtml("an open ` quote")).toBe("an open ` quote");
    expect(markdownToHtml("[a] (b) [c](d")).toBe("[a] (b) [c](d");
    expect(markdownToHtml("[a] or [b](/c)")).toBe('[a] or <a href="/c">b</a>');
    
  9. closes each marker at the first closing marker after it
    expect(markdownToHtml("**a** b**")).toBe("<strong>a</strong> b**");
    expect(markdownToHtml("*a* b*")).toBe("<em>a</em> b*");
    expect(markdownToHtml("`a` b `c`")).toBe("<code>a</code> b <code>c</code>");
    expect(markdownToHtml("[a](/x) b)")).toBe('<a href="/x">a</a> b)');
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).