Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve whitespace in code blocks #226

Merged
merged 3 commits into from
Sep 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions packages/mdx/mdx-hast-to-jsx.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
function toJSX(node, parentNode = {}, options = {}) {
const {
// default options
skipExport = false,
preserveNewlines = false,
} = options
let children = ''

if (node.type === 'root') {
Expand Down Expand Up @@ -36,7 +41,7 @@ function toJSX(node, parentNode = {}, options = {}) {
'\n' +
exportNodes.map(childNode => toJSX(childNode, node)).join('\n') +
'\n' +
(options.skipExport
(skipExport
? ''
: 'export default ({components, ...props}) => ') +
`<MDXTag name="wrapper" ${
Expand All @@ -49,7 +54,14 @@ function toJSX(node, parentNode = {}, options = {}) {

// recursively walk through children
if (node.children) {
children = node.children.map(childNode => toJSX(childNode, node)).join('')
children = node.children.map(childNode => {
const childOptions = {
...options,
// tell all children inside <pre> tags to preserve newlines as text nodes
preserveNewlines: preserveNewlines || node.tagName === 'pre',
}
return toJSX(childNode, node, childOptions)
}).join('')
}

if (node.type === 'element') {
Expand All @@ -68,11 +80,14 @@ function toJSX(node, parentNode = {}, options = {}) {
}${props ? ` props={${props}}` : ''}>${children}</MDXTag>`
}

// Wraps all text nodes except new lines inside template string, so that we don't run into escaping issues.
// Wraps text nodes inside template string, so that we don't run into escaping issues.
if (node.type === 'text') {
return node.value === '\n'
? node.value
: '{`' + node.value.replace(/`/g, '\\`').replace(/\$/g, '\\$') + '`}'
// Don't wrap newlines unless specifically instructed to by the flag,
// to avoid issues like React warnings caused by text nodes in tables.
if (node.value === '\n' && !preserveNewlines) {
return node.value
}
return '{`' + node.value.replace(/`/g, '\\`').replace(/\$/g, '\\$') + '`}'
}

if (node.type === 'import' || node.type === 'export' || node.type === 'jsx') {
Expand Down
1 change: 1 addition & 0 deletions packages/mdx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@babel/core": "^7.0.0-beta.44",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0-beta.44",
"@babel/plugin-syntax-jsx": "^7.0.0-beta.44",
"@mapbox/rehype-prism": "^0.2.0",
"hast-util-select": "^1.0.1",
"jest": "^22.4.3",
"request-image-size": "^2.1.0"
Expand Down
54 changes: 28 additions & 26 deletions packages/mdx/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const fs = require('fs')
const path = require('path')
const { select } = require('hast-util-select')
const requestImageSize = require('request-image-size')
const prism = require('@mapbox/rehype-prism')

const fixtureBlogPost = fs.readFileSync(
path.join(__dirname, './fixtures/blog-post.md')
Expand Down Expand Up @@ -49,11 +50,20 @@ it('Should render blockquote correctly', async () => {
it('Should render HTML inside inlineCode correctly', async () => {
const result = await mdx('`<div>`')

expect(
result.includes(
'<MDXTag name="inlineCode" components={components} parentName="p">{`<div>`}</MDXTag>'
)
).toBeTruthy()
expect(result).toContain(
'<MDXTag name="inlineCode" components={components} parentName="p">{`<div>`}</MDXTag>'
)
})

it('Should preserve newlines in code blocks', async () => {
const result = await mdx(`
\`\`\`dockerfile
# Add main script
COPY start.sh /home/start.sh
\`\`\`
`, { hastPlugins: [prism] })

expect(result).toContain('{`# Add main script`}</MDXTag>{`\n`}')
})

it('Should support comments', async () => {
Expand All @@ -68,27 +78,23 @@ A paragraph
<!-- a nested Markdown comment -->
</div>
`)
expect(result.includes('{/* a Markdown comment */}')).toBeTruthy()
expect(result.includes('<!-- a code block Markdown comment -->')).toBeTruthy()
expect(result.includes('{/* a nested JSX comment */}')).toBeTruthy()
expect(result.includes('{/* a nested Markdown comment */}')).toBeTruthy()
expect(result).toContain('{/* a Markdown comment */}')
expect(result).toContain('<!-- a code block Markdown comment -->')
expect(result).toContain('{/* a nested JSX comment */}')
expect(result).toContain('{/* a nested Markdown comment */}')
})

it('Should not include export wrapper if skipExport is true', async () => {
const result = await mdx('> test\n\n> `test`', { skipExport: true })

expect(
result.includes('export default ({components, ...props}) =>')
).toBeFalsy()
expect(result).not.toContain('export default ({components, ...props}) =>')
})

it('Should recognize components as properties', async () => {
const result = await mdx('# Hello\n\n<MDX.Foo />')
expect(
result.includes(
'<MDXTag name="h1" components={components}>{`Hello`}</MDXTag>\n<MDX.Foo />'
)
).toBeTruthy()
expect(result).toContain(
'<MDXTag name="h1" components={components}>{`Hello`}</MDXTag>\n<MDX.Foo />'
)
})

it('Should render elements without wrapping blank new lines', async () => {
Expand All @@ -97,7 +103,7 @@ it('Should render elements without wrapping blank new lines', async () => {
| :--- | :---- |
| Col1 | Col2 |`)

expect(result.includes('{`\n`}')).toBe(false)
expect(result).not.toContain('{`\n`}')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tenor

})

test('Should await and render async plugins', async () => {
Expand All @@ -123,16 +129,12 @@ test(
'This is a paragraph with a [^footnote]\n\n[^footnote]: Here is the footnote'
)

expect(
result.includes(
'<MDXTag name="sup" components={components} parentName="p" props={{"id":"fnref-footnote"}}>'
)
expect(result).toContain(
'<MDXTag name="sup" components={components} parentName="p" props={{"id":"fnref-footnote"}}>'
)

expect(
result.includes(
'<MDXTag name="li" components={components} parentName="ol" props={{"id":"fn-footnote"}}>'
)
expect(result).toContain(
'<MDXTag name="li" components={components} parentName="ol" props={{"id":"fn-footnote"}}>'
)
},
10000
Expand Down
Loading