diff --git a/packages/gatsby-remark-prismjs/README.md b/packages/gatsby-remark-prismjs/README.md index 600191a1d1920..4a3e5f0851e16 100644 --- a/packages/gatsby-remark-prismjs/README.md +++ b/packages/gatsby-remark-prismjs/README.md @@ -75,6 +75,10 @@ plugins: [ host: "localhost", global: false, }, + // By default the HTML entities <>&'" are escaped. + // Add additional HTML escapes by providing a mapping + // of HTML entities and their escape value IE: { '}': '{' } + escapeEntities: {}, }, }, ], diff --git a/packages/gatsby-remark-prismjs/src/__tests__/escape-html.js b/packages/gatsby-remark-prismjs/src/__tests__/escape-html.js new file mode 100644 index 0000000000000..549883af87880 --- /dev/null +++ b/packages/gatsby-remark-prismjs/src/__tests__/escape-html.js @@ -0,0 +1,26 @@ +const escapeHtml = require(`../escape-html`) + +describe(`escaping html entities`, () => { + it(`escapes base html when no additional escapes are passed in`, () => { + const code = `hello world&><"'` + expect(escapeHtml(code)).toBe(`hello world&><"'`) + }) + + it(`escapes additional html entities when passed in`, () => { + const code = `hello world{` + const mapping = { + "{": `{`, + } + expect(escapeHtml(code, mapping)).toBe(`hello world{`) + }) + + it(`escapes base html entities and additional html entities`, () => { + const code = `hello world&><"'{` + const mapping = { + "{": `{`, + } + expect(escapeHtml(code, mapping)).toBe( + `hello world&><"'{` + ) + }) +}) diff --git a/packages/gatsby-remark-prismjs/src/__tests__/highlight-code.js b/packages/gatsby-remark-prismjs/src/__tests__/highlight-code.js index 4928b422ea4b0..12758649854fc 100644 --- a/packages/gatsby-remark-prismjs/src/__tests__/highlight-code.js +++ b/packages/gatsby-remark-prismjs/src/__tests__/highlight-code.js @@ -13,7 +13,7 @@ int sum(a, b) { } ` expect( - highlightCode(language, code, lineNumbersHighlight) + highlightCode(language, code, {}, lineNumbersHighlight) ).toMatchSnapshot() }) @@ -48,7 +48,7 @@ class Counter extends React.Component { export default Counter ` - const processed = highlightCode(language, code, lineNumbersHighlight) + const processed = highlightCode(language, code, {}, lineNumbersHighlight) expect(processed).toMatchSnapshot() // expect spans to not contain \n as it would break line highlighting @@ -62,7 +62,7 @@ export default Counter const highlightCode = require(`../highlight-code`) const language = `text` const code = `` - expect(highlightCode(language, code, [], true)).toMatch( + expect(highlightCode(language, code, {}, [], true)).toMatch( `<button />` ) expect(console.warn).toHaveBeenCalledWith( @@ -141,7 +141,9 @@ export default Counter const language = `javascript` const linesToHighlight = [1] const code = `const a = 1\nconst b = 2` - expect(highlightCode(language, code, linesToHighlight)).not.toMatch(/\n$/) + expect(highlightCode(language, code, {}, linesToHighlight)).not.toMatch( + /\n$/ + ) }) it(`a trailing newline is preserved`, () => { @@ -149,7 +151,7 @@ export default Counter const language = `javascript` const linesToHighlight = [1] const code = `const a = 1\nconst b = 2\n` - expect(highlightCode(language, code, linesToHighlight)).toMatch( + expect(highlightCode(language, code, {}, linesToHighlight)).toMatch( /[^\n]\n$/ ) }) diff --git a/packages/gatsby-remark-prismjs/src/escape-html.js b/packages/gatsby-remark-prismjs/src/escape-html.js new file mode 100644 index 0000000000000..b5f25cc5f1188 --- /dev/null +++ b/packages/gatsby-remark-prismjs/src/escape-html.js @@ -0,0 +1,26 @@ +module.exports = (code, additionalHtmlEscapes = {}) => { + const baseHTMLEscapes = { + "&": `&`, + ">": `>`, + "<": `<`, + '"': `"`, + "'": `'`, + } + + const htmlEscapes = { + ...additionalHtmlEscapes, + ...baseHTMLEscapes, + } + + const escapedChars = char => htmlEscapes[char] + + const chars = Object.keys(htmlEscapes) + + const charsRe = new RegExp(`[${chars.join()}]`, `g`) + + const rehasUnescapedChars = new RegExp(charsRe.source) + + return code && rehasUnescapedChars.test(code) + ? code.replace(charsRe, escapedChars) + : code +} diff --git a/packages/gatsby-remark-prismjs/src/highlight-code.js b/packages/gatsby-remark-prismjs/src/highlight-code.js index e5ddaaef0d732..76b4236120916 100644 --- a/packages/gatsby-remark-prismjs/src/highlight-code.js +++ b/packages/gatsby-remark-prismjs/src/highlight-code.js @@ -1,12 +1,13 @@ const Prism = require(`prismjs`) -const _ = require(`lodash`) const loadPrismLanguage = require(`./load-prism-language`) const handleDirectives = require(`./directives`) +const escapeHTML = require(`./escape-html`) const unsupportedLanguages = new Set() module.exports = ( language, code, + additionalEscapeCharacters = {}, lineNumbersHighlight = [], noInlineHighlight = false ) => { @@ -34,7 +35,7 @@ module.exports = ( console.warn(message, `applying generic code block`) unsupportedLanguages.add(lang) } - return _.escape(code) + return escapeHTML(code, additionalEscapeCharacters) } } diff --git a/packages/gatsby-remark-prismjs/src/index.js b/packages/gatsby-remark-prismjs/src/index.js index 7dae4be862efd..2f5b899be3d00 100644 --- a/packages/gatsby-remark-prismjs/src/index.js +++ b/packages/gatsby-remark-prismjs/src/index.js @@ -1,5 +1,4 @@ const visit = require(`unist-util-visit`) - const parseOptions = require(`./parse-options`) const loadLanguageExtension = require(`./load-prism-language-extension`) const highlightCode = require(`./highlight-code`) @@ -20,6 +19,7 @@ module.exports = ( host: `localhost`, global: false, }, + escapeEntities = {}, } = {} ) => { const normalizeLanguage = lang => { @@ -94,7 +94,7 @@ module.exports = ( + `
`
+ ``
+ `${useCommandLine ? commandLine(node.value, outputLines, promptUser, promptHost) : ``}`
- + `${highlightCode(languageName, node.value, highlightLines, noInlineHighlight)}`
+ + `${highlightCode(languageName, node.value, escapeEntities, highlightLines, noInlineHighlight)}`
+ `
`
+ `${numLinesNumber}`
+ `
`
@@ -118,7 +118,8 @@ module.exports = (
node.type = `html`
node.value = `${highlightCode(
languageName,
- node.value
+ node.value,
+ escapeEntities
)}
`
})
}