UNPKG

micro-mdx-parser

Version:

A tiny parser to convert markdown or html into JSON

1,273 lines (1,177 loc) 31 kB
const { inspect } = require('util') const { test } = require('uvu') const assert = require('uvu/assert') const { parse, stringify, validate } = require('.') const { getTextBetweenChars } = require('./utils') function deepLog(objOrLabel, logVal) { let obj = objOrLabel if (typeof objOrLabel === 'string') { obj = logVal console.log(objOrLabel) } console.log(inspect(obj, false, null, true)) } function replaceTextBetweenChars(str = '', start, end, newStr) { return str.substring(0, start) + newStr + str.substring(end) } test('Exports API', () => { assert.equal(typeof parse, 'function') assert.equal(typeof stringify, 'function') }) const stringExample = ` Hello, world! Below is an example of markdown in JSX. <div style={{padding: '1rem', backgroundColor: 'violet'}}> Try and change the background color to. </div> <MyComponent isCool /> <img src="w3schools.jpg" alt="W3Schools-img.com" width="104" height="142" /> ` const moreComplexStringExample = ` Hello, world! Below is an example of markdown in JSX. **Bold text** next to *italic text* and an example of <u>underlined text</u> & <mark>highlighting</mark> via html tag. <div style={{padding: '1rem', backgroundColor: 'violet'}}> Try and change the background color to. </div> <MyComponent isCool /> <img src="w3schools.jpg" alt="W3Schools-img.com" width="104" height="142" /> ` function escapeStringRegexp(string) { // Escape characters with special meaning either inside or outside character sets. // Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar. return string.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d') } function isInline(match, str) { const pattern = new RegExp(`(\\s*)${escapeStringRegexp(str)}(\\s*)`) const found = stringExample.match(pattern) if (found) { console.log('found leading/trailing', found) } } test('simple md', () => { const parsedValue = parse(stringExample) /* console.log('parsedValue') deepLog(parsedValue) console.log(getTextBetweenChars(stringExample, 189, 267)) process.exit(1) /** */ const html = stringify(parsedValue) /* console.log('html') console.log(html) process.exit(1) /** */ assert.equal(parsedValue, [ { type: 'text', content: '\nHello, world!\nBelow is an example of markdown in JSX.\n\n', position: { start: { index: 0, line: 1, column: 1 }, end: { index: 56, line: 5, column: 1 } } }, { type: 'element', tagName: 'div', tagValue: "<div style={{padding: '1rem', backgroundColor: 'violet'}}>\n" + ' Try and change the background color to.\n' + '</div>', props: { style: { padding: '1rem', backgroundColor: 'violet' } }, propsRaw: " style={{padding: '1rem', backgroundColor: 'violet'}}", children: [ { type: 'text', content: '\n Try and change the background color to.\n', position: { start: { index: 114, line: 5, column: 58 }, end: { index: 157, line: 7, column: 1 } } } ], position: { start: { index: 56, line: 5, column: 1 }, end: { index: 163, line: 7, column: 6 } } }, { type: 'text', content: '\n\n', position: { start: { index: 163, line: 7, column: 6 }, end: { index: 165, line: 9, column: 1 } } }, { type: 'component', tagName: 'MyComponent', tagValue: '<MyComponent isCool />', props: { isCool: true }, propsRaw: ' isCool ', children: [], isSelfClosing: true, position: { start: { index: 165, line: 9, column: 1 }, end: { index: 187, line: 9, column: 22 } } }, { type: 'text', content: '\n\n', position: { start: { index: 187, line: 9, column: 22 }, end: { index: 189, line: 11, column: 1 } } }, { type: 'element', tagName: 'img', tagValue: '<img src="w3schools.jpg" alt="W3Schools-img.com" width="104" height="142" />', props: { src: 'w3schools.jpg', alt: 'W3Schools-img.com', width: '104', height: '142' }, propsRaw: ' src="w3schools.jpg" alt="W3Schools-img.com" width="104" height="142" ', children: [], isSelfClosing: true, position: { start: { index: 189, line: 11, column: 1 }, end: { index: 265, line: 11, column: 76 } } }, { type: 'text', content: '\n\n', position: { start: { index: 265, line: 11, column: 76 }, end: { index: 267, line: 13, column: 1 } } } ]) assert.equal(html, stringExample, 'html') const myComponent = parsedValue.find((section) => section.tagName === 'MyComponent') //console.log('myComponent', myComponent) assert.equal(myComponent, { type: 'component', tagName: 'MyComponent', tagValue: '<MyComponent isCool />', props: { isCool: true }, propsRaw: ' isCool ', children: [], isSelfClosing: true, position: { start: { index: 165, line: 9, column: 1 }, end: { index: 187, line: 9, column: 22 } } }, 'found component is equal') const componentText = getTextBetweenChars(stringExample, 165, 187) // console.log('componentText') // console.log(componentText) }) const selfClosingTags = ` ## Testing images <MyComponent isCool/> ` test('verify isSelfClosing set', () => { const parsedValue = parse(selfClosingTags) /* console.log('parsedValue') deepLog(parsedValue) console.log(`"${getTextBetweenChars(selfClosingTags, 20, 41)}"`) console.log(`${replaceTextBetweenChars(selfClosingTags, 20, 41, 'My thing')}`) // console.log(`"${stringify(parsedValue)}"`) process.exit(1) /** */ assert.equal(parsedValue, [ { type: 'text', content: '\n## Testing images\n\n', position: { start: { index: 0, line: 1, column: 1 }, end: { index: 20, line: 4, column: 1 } } }, { type: 'component', tagName: 'MyComponent', tagValue: '<MyComponent isCool/>', props: { isCool: true }, propsRaw: ' isCool', children: [], isSelfClosing: true, position: { start: { index: 20, line: 4, column: 1 }, end: { index: 41, line: 4, column: 21 } } }, { type: 'text', content: '\n', position: { start: { index: 41, line: 4, column: 21 }, end: { index: 42, line: 5, column: 1 } } } ]) }) const withComments = ` ## Hi There <!-- hehehhehehe --> Cool ` test('withComments', () => { const parsedValue = parse(withComments) const commentText = getTextBetweenChars(withComments, 15, 35) /* console.log('commentText') console.log(commentText) deepLog(parsedValue) process.exit(1) /** */ assert.equal(parsedValue, [ { type: 'text', content: '\n## Hi\n\nThere\n\n', position: { start: { index: 0, line: 1, column: 1 }, end: { index: 15, line: 6, column: 1 } } }, { type: 'comment', content: ' hehehhehehe ', position: { start: { index: 15, line: 6, column: 1 }, end: { index: 35, line: 6, column: 20 } } }, { type: 'text', content: '\n\nCool\n', position: { start: { index: 35, line: 6, column: 20 }, end: { index: 42, line: 9, column: 1 } } } ]) }) const invalidImgTag = ` Hello, world! Below is an example of markdown in JSX. <img src="cool.jpg" alt="nice-img.com" width="104" height="142" > <div style={{padding: '1rem', backgroundColor: 'violet'}}> Try and change the background color to. </div> <MyComponent isCool /> <img src="w3schools.jpg" alt="W3Schools-img.com" width="104" height="142" > ` test('Validate markup', () => { const parsedValue = parse(invalidImgTag) const errors = validate(parsedValue) const errorsViaStr = validate(invalidImgTag) /* deepLog(errors) deepLog(errorsViaStr) process.exit(1) /** */ const answer = [ { message: 'Missing closing "/>" on "element". <img src="cool.jpg" alt="nice-img.com" width="104" height="142" >', value: '<img src="cool.jpg" alt="nice-img.com" width="104" height="142" >', position: { start: { index: 56, line: 5, column: 1 }, end: { index: 121, line: 5, column: 65 } } }, { message: 'Missing closing "/>" on "element". <img src="w3schools.jpg" alt="W3Schools-img.com" width="104" height="142" >', value: '<img src="w3schools.jpg" alt="W3Schools-img.com" width="104" height="142" >', position: { start: { index: 256, line: 13, column: 1 }, end: { index: 331, line: 13, column: 75 } } } ] assert.equal(errors.length, 2) assert.equal(errorsViaStr.length, 2) assert.equal(errors, answer) assert.equal(errorsViaStr, answer, 'errorsViaStr') }) test('inline <https://www.markdown-link-test.org> links', () => { const md = ` <div>gfoo</div> <div> </div> <https://www.markdownguide.org> <fake@example.com> ` const parsedValue = parse(md) // deepLog(parsedValue) assert.equal(parsedValue, [ { type: 'text', content: '\n', position: { start: { index: 0, line: 1, column: 1 }, end: { index: 1, line: 2, column: 1 } } }, { type: 'element', tagName: 'div', tagValue: '<div>gfoo</div>', props: {}, propsRaw: '', children: [ { type: 'text', content: 'gfoo', position: { start: { index: 6, line: 2, column: 5 }, end: { index: 10, line: 2, column: 9 } } } ], position: { start: { index: 1, line: 2, column: 1 }, end: { index: 16, line: 2, column: 15 } } }, { type: 'text', content: '\n\n ', position: { start: { index: 16, line: 2, column: 15 }, end: { index: 20, line: 4, column: 2 } } }, { type: 'element', tagName: 'div', tagValue: '<div> </div>', props: {}, propsRaw: '', children: [ { type: 'text', content: ' ', position: { start: { index: 25, line: 4, column: 7 }, end: { index: 26, line: 4, column: 8 } } } ], position: { start: { index: 20, line: 4, column: 2 }, end: { index: 32, line: 4, column: 14 } } }, { type: 'text', content: '\n \n <https://www.markdownguide.org>\n <fake@example.com>\n', position: { start: { index: 32, line: 4, column: 14 }, end: { index: 91, line: 8, column: 1 } } } ]) }) test('Single line react style props', () => { const singleLineProps = ` <div id="nice" style={{ padding: '1rem', backgroundColor: 'violet' }}> Try and change the background color to. </div> `.trim() const parsedValue = parse(singleLineProps) // deepLog(parsedValue) assert.equal(parsedValue, [ { type: 'element', tagName: 'div', tagValue: `<div id="nice" style={{ padding: '1rem', backgroundColor: 'violet' }}>\n` + ' Try and change the background color to.\n' + '</div>', props: { id: 'nice', style: { padding: '1rem', backgroundColor: 'violet' } }, propsRaw: ` id="nice" style={{ padding: '1rem', backgroundColor: 'violet' }}`, children: [ { type: 'text', content: '\n Try and change the background color to.\n', position: { start: { index: 70, line: 1, column: 71 }, end: { index: 113, line: 3, column: 1 } } } ], position: { start: { index: 0, line: 1, column: 1 }, end: { index: 119, line: 3, column: 6 } } } ]) const divText = getTextBetweenChars(singleLineProps, parsedValue[0].position.start.index, parsedValue[0].position.end.index) assert.equal(divText, singleLineProps, 'divText matches position indexes') }) test('inner component props {()}', () => { const multiLineProps = ` <div innerComponent={( <span>cool</span> )} > Try and change the background color to. </div> ` const parsedValue = parse(multiLineProps) // deepLog(parsedValue) }) test('Handles inline functions', () => { const inlineFn = ` <div onClick={() => console.log('hi')}> click </div> ` const parsedValue = parse(inlineFn) // deepLog(parsedValue) assert.equal(parsedValue[1].props.onClick, "() => console.log('hi')") }) test('Handles multiline functions', () => { const inlineFn = ` <div onClick={(arg) => { console.log('hi') }} > click </div> ` const parsedValue = parse(inlineFn) // deepLog(parsedValue) assert.equal(parsedValue[1].props.onClick, `(arg) => { console.log('hi') }`) }) test('Handles multiline functions two', () => { const inlineFn = ` <div onClick={ () => { console.log('hi') } }> click </div> ` const parsedValue = parse(inlineFn) // deepLog(parsedValue) assert.equal(parsedValue[1].props.onClick, ` () => { console.log('hi') } `) }) const b = ` <div onClick={() => console.log('hi')}> click </div> <div onClick={ () => { console.log('hi') } }> click </div> ` test('inner component props {()}', () => { const broken = ` <Nested customElement={( <div>Custom div</div> )} htmlContent={( <span style={{ color: 'red' }} color="b'lue">This should be red too</span> )} > check it </Nested> ` const parsedValue = parse(broken) // deepLog(parsedValue) assert.equal(parsedValue[1].props, { customElement: '\n <div>Custom div</div>\n ', htmlContent: '\n' + ` <span style={{ color: 'red' }} color="b'lue">This should be red too</span>\n` + ' ' }, 'props match' ) assert.equal(parsedValue[1].propsRaw, '\n' + ' customElement={(\n' + ' <div>Custom div</div>\n' + ' )}\n' + ' htmlContent={(\n' + ` <span style={{ color: 'red' }} color="b'lue">This should be red too</span>\n` + ' )}\n', 'propsRaw match' ) }) test.skip('inner component props {{}}', () => { const broken = ` <Nested buttonText={{<span>Nice. dudeee</span>}}>\n check it\n</Nested> ` const parsedValue = parse(broken) deepLog(parsedValue) }) test('Handles multiline components with inner html props', () => { const broken = ` <Nested customElement={( <div>Custom div</div> )} htmlContent={( <span style={{ color: 'red' }}>This should be red too</span> )} customElementTwo={( <div id="foo" style={{ color: 'purple' }}> This should be purple too <div style={{ color: 'green' }}> This should be red too </div> </div> )} > check it </Nested> ` const parsedValue = parse(broken) /* deepLog(parsedValue) /** */ assert.equal(parsedValue, [ { type: 'text', content: '\n', position: { start: { index: 0, line: 1, column: 1 }, end: { index: 1, line: 2, column: 1 } } }, { type: 'component', tagName: 'Nested', tagValue: '<Nested\n' + ' customElement={(\n' + ' <div>Custom div</div>\n' + ' )}\n' + ' htmlContent={(\n' + " <span style={{ color: 'red' }}>This should be red too</span>\n" + ' )}\n' + ' customElementTwo={(\n' + ` <div id="foo" style={{ color: 'purple' }}>\n` + ' This should be purple too\n' + " <div style={{ color: 'green' }}>\n" + ' This should be red too\n' + ' </div>\n' + ' </div>\n' + ' )}\n' + '>\n' + ' check it\n' + '</Nested>', props: { customElement: '\n <div>Custom div</div>\n ', htmlContent: "\n <span style={{ color: 'red' }}>This should be red too</span>\n ", customElementTwo: '\n' + ` <div id="foo" style={{ color: 'purple' }}>\n` + ' This should be purple too\n' + " <div style={{ color: 'green' }}>\n" + ' This should be red too\n' + ' </div>\n' + ' </div>\n' + ' ' }, propsRaw: '\n' + ' customElement={(\n' + ' <div>Custom div</div>\n' + ' )}\n' + ' htmlContent={(\n' + " <span style={{ color: 'red' }}>This should be red too</span>\n" + ' )}\n' + ' customElementTwo={(\n' + ` <div id="foo" style={{ color: 'purple' }}>\n` + ' This should be purple too\n' + " <div style={{ color: 'green' }}>\n" + ' This should be red too\n' + ' </div>\n' + ' </div>\n' + ' )}\n', children: [ { type: 'text', content: '\n check it\n', position: { start: { index: 347, line: 17, column: 1 }, end: { index: 359, line: 19, column: 1 } } } ], position: { start: { index: 1, line: 2, column: 1 }, end: { index: 368, line: 19, column: 9 } } }, { type: 'text', content: '\n', position: { start: { index: 368, line: 19, column: 9 }, end: { index: 369, line: 20, column: 1 } } } ]) }) test('Multiline props', () => { const multiLineProps = ` Hello, world! <div style={{ padding: '1rem', backgroundColor: 'violet' }} > Try and change the background color to. </div> ` const parsedValue = parse(multiLineProps) // deepLog(parsedValue) // console.log(getTextBetweenChars(multiLineProps, 16, 133)) assert.equal(parsedValue, [ { type: 'text', content: '\nHello, world!\n\n', position: { start: { index: 0, line: 1, column: 1 }, end: { index: 16, line: 4, column: 1 } } }, { type: 'element', tagName: 'div', tagValue: '<div \n' + ' style={{\n' + " padding: '1rem', \n" + " backgroundColor: 'violet'\n" + ' }}\n' + '>\n' + ' Try and change the background color to.\n' + '</div>', props: { style: { padding: '1rem', backgroundColor: 'violet' } }, propsRaw: ' \n' + ' style={{\n' + " padding: '1rem', \n" + " backgroundColor: 'violet'\n" + ' }}\n', children: [ { type: 'text', content: '\n Try and change the background color to.\n', position: { start: { index: 94, line: 9, column: 1 }, end: { index: 137, line: 11, column: 1 } } } ], position: { start: { index: 16, line: 4, column: 1 }, end: { index: 143, line: 11, column: 6 } } }, { type: 'text', content: '\n', position: { start: { index: 143, line: 11, column: 6 }, end: { index: 144, line: 12, column: 1 } } } ]) }) test('Multiline props two', () => { const multiLineProps = ` Hello, world! <div style={{ padding: '1rem', backgroundColor: 'violet' }}> Try and change the background color to. </div> <MyComponent isCool={\` multi line \`} /> <MyComponentTwoIndents isCool={\` multi line \`} /> xyz ` const parsedValue = parse(multiLineProps) // deepLog(parsedValue) assert.equal(parsedValue, [ { type: 'text', content: '\nHello, world!\n\n', position: { start: { index: 0, line: 1, column: 1 }, end: { index: 16, line: 4, column: 1 } } }, { type: 'element', tagName: 'div', tagValue: '<div style={{\n' + " padding: '1rem', \n" + " backgroundColor: 'violet'\n" + '}}>\n' + ' Try and change the background color to.\n' + '</div>', props: { style: { padding: '1rem', backgroundColor: 'violet' } }, propsRaw: " style={{\n padding: '1rem', \n backgroundColor: 'violet'\n}}", children: [ { type: 'text', content: '\n Try and change the background color to.\n', position: { start: { index: 81, line: 7, column: 3 }, end: { index: 124, line: 9, column: 1 } } } ], position: { start: { index: 16, line: 4, column: 1 }, end: { index: 130, line: 9, column: 6 } } }, { type: 'text', content: '\n\n', position: { start: { index: 130, line: 9, column: 6 }, end: { index: 132, line: 11, column: 1 } } }, { type: 'component', tagName: 'MyComponent', tagValue: '<MyComponent isCool={`\nmulti\nline\n`} />', props: { isCool: '\nmulti\nline\n' }, propsRaw: ' isCool={`\nmulti\nline\n`} ', children: [], isSelfClosing: true, position: { start: { index: 132, line: 11, column: 1 }, end: { index: 171, line: 14, column: 5 } } }, { type: 'text', content: '\n\n', position: { start: { index: 171, line: 14, column: 5 }, end: { index: 173, line: 16, column: 1 } } }, { type: 'component', tagName: 'MyComponentTwoIndents', tagValue: '<MyComponentTwoIndents isCool={`\n multi\n line\n`} />', props: { isCool: '\n multi\n line\n' }, propsRaw: ' isCool={`\n multi\n line\n`} ', children: [], isSelfClosing: true, position: { start: { index: 173, line: 16, column: 1 }, end: { index: 226, line: 19, column: 5 } } }, { type: 'text', content: '\n\nxyz\n', position: { start: { index: 226, line: 19, column: 5 }, end: { index: 232, line: 22, column: 1 } } } ]) }) test('Innner jsx props', () => { const inlineComponentProp = ` <Nested buttonText={\`<span>Niceeee</span>\`}> check it </Nested> ` const inlineComponentPropTwo = ` <Nested buttonText={<span>Niceeee</span>}> check it </Nested> ` const parsedValue = parse(inlineComponentPropTwo) // deepLog(parsedValue) // process.exit(1) assert.equal(parsedValue, [ { type: 'text', content: '\n ', position: { start: { index: 0, line: 1, column: 1 }, end: { index: 3, line: 2, column: 2 } } }, { type: 'component', tagName: 'Nested', tagValue: '<Nested buttonText={<span>Niceeee</span>}>\n check it\n </Nested>', props: { buttonText: '<span>Niceeee</span>' }, propsRaw: ' buttonText={<span>Niceeee</span>}', children: [ { type: 'text', content: '\n check it\n ', position: { start: { index: 45, line: 2, column: 44 }, end: { index: 61, line: 4, column: 2 } } } ], position: { start: { index: 3, line: 2, column: 2 }, end: { index: 70, line: 4, column: 11 } } }, { type: 'text', content: '\n ', position: { start: { index: 70, line: 4, column: 11 }, end: { index: 73, line: 5, column: 2 } } } ]) }) const HTML_WITH_NESTED_COMPONENTS = ` Now <button>xcool</button> I can authorxxx with stuff brokjen <Builder components={[{ type: "content", content: "Content here...\n\n<Builder\n components={[{ type: \"content\", content: \"Content here... woah\" }]}\n/>" }]} /> <Builder beans=true>cool</Builder> <OBuilder components={[{ type: "content", content: "Content here...\n\n<Builder\n components={[{\n type: \"content\",\n content: \"Content here... woah nice\\n\\n<Builder\\n components={[{ type: \\\"content\\\", content: \\\"Content here... deeeeep\\\" }]}\\n/>\"\n }]}\n/>" }]} /> <MyComponent isCool={\` multi line \`} /> <MyComponentTwoIndents isCool={\` multi line \`} more pros=true /> <img src="w3schools.jpg" alt="W3Schools-img.com" width="104" height="142" /> <Bool>cool</Bool> ` test('HTML_WITH_NESTED_COMPONENTS', () => { const parsedValue = parse(HTML_WITH_NESTED_COMPONENTS) /* deepLog('nested', parsedValue) /** */ assert.equal(parsedValue, [ { type: 'text', content: '\nNow\n\n', position: { start: { index: 0, line: 1, column: 1 }, end: { index: 6, line: 4, column: 1 } } }, { type: 'element', tagName: 'button', tagValue: '<button>xcool</button>', props: {}, propsRaw: '', children: [ { type: 'text', content: 'xcool', position: { start: { index: 14, line: 4, column: 8 }, end: { index: 19, line: 4, column: 13 } } } ], position: { start: { index: 6, line: 4, column: 1 }, end: { index: 28, line: 4, column: 22 } } }, { type: 'text', content: '\n\nI can authorxxx with stuff brokjen\n\n', position: { start: { index: 28, line: 4, column: 22 }, end: { index: 66, line: 8, column: 1 } } }, { type: 'component', tagName: 'Builder', tagValue: '<Builder\n' + ' components={[{\n' + ' type: "content",\n' + ' content: "Content here...\n' + '\n' + '<Builder\n' + ' components={[{ type: "content", content: "Content here... woah" }]}\n' + '/>"\n' + ' }]}\n' + '/>', props: { components: [ { type: 'content', content: 'Content here...\n' + '\n' + '<Builder\n' + ' components={[{ type: "content", content: "Content here... woah" }]}\n' + '/>' } ] }, propsRaw: '\n' + ' components={[{\n' + ' type: "content",\n' + ' content: "Content here...\n' + '\n' + '<Builder\n' + ' components={[{ type: "content", content: "Content here... woah" }]}\n' + '/>"\n' + ' }]}\n', children: [], isSelfClosing: true, position: { start: { index: 66, line: 8, column: 1 }, end: { index: 235, line: 17, column: 2 } } }, { type: 'text', content: '\n\n', position: { start: { index: 235, line: 17, column: 2 }, end: { index: 237, line: 19, column: 1 } } }, { type: 'component', tagName: 'Builder', tagValue: '<Builder beans=true>cool</Builder>', props: { beans: true }, propsRaw: ' beans=true', children: [ { type: 'text', content: 'cool', position: { start: { index: 257, line: 19, column: 20 }, end: { index: 261, line: 19, column: 24 } } } ], position: { start: { index: 237, line: 19, column: 1 }, end: { index: 271, line: 19, column: 34 } } }, { type: 'text', content: '\n\n', position: { start: { index: 271, line: 19, column: 34 }, end: { index: 273, line: 21, column: 1 } } }, { type: 'component', tagName: 'OBuilder', tagValue: '<OBuilder\n' + ' components={[{\n' + ' type: "content",\n' + ' content: "Content here...\n' + '\n' + '<Builder\n' + ' components={[{\n' + ' type: "content",\n' + ' content: "Content here... woah nice\\n\\n<Builder\\n components={[{ type: \\"content\\", content: \\"Content here... deeeeep\\" }]}\\n/>"\n' + ' }]}\n' + '/>"\n' + ' }]}\n' + '/>', props: { components: [ { type: 'content', content: 'Content here...\n' + '\n' + '<Builder\n' + ' components={[{\n' + ' type: "content",\n' + ' content: "Content here... woah nice\n' + '\n' + '<Builder\n' + ' components={[{ type: "content", content: "Content here... deeeeep" }]}\n' + '/>"\n' + ' }]}\n' + '/>' } ] }, propsRaw: '\n' + ' components={[{\n' + ' type: "content",\n' + ' content: "Content here...\n' + '\n' + '<Builder\n' + ' components={[{\n' + ' type: "content",\n' + ' content: "Content here... woah nice\\n\\n<Builder\\n components={[{ type: \\"content\\", content: \\"Content here... deeeeep\\" }]}\\n/>"\n' + ' }]}\n' + '/>"\n' + ' }]}\n', children: [], isSelfClosing: true, position: { start: { index: 273, line: 21, column: 1 }, end: { index: 552, line: 33, column: 2 } } }, { type: 'text', content: '\n\n', position: { start: { index: 552, line: 33, column: 2 }, end: { index: 554, line: 35, column: 1 } } }, { type: 'component', tagName: 'MyComponent', tagValue: '<MyComponent isCool={`\nmulti\nline\n`} />', props: { isCool: '\nmulti\nline\n' }, propsRaw: ' isCool={`\nmulti\nline\n`} ', children: [], isSelfClosing: true, position: { start: { index: 554, line: 35, column: 1 }, end: { index: 593, line: 38, column: 5 } } }, { type: 'text', content: '\n\n', position: { start: { index: 593, line: 38, column: 5 }, end: { index: 595, line: 40, column: 1 } } }, { type: 'component', tagName: 'MyComponentTwoIndents', tagValue: '<MyComponentTwoIndents isCool={`\n multi\n line\n`} more pros=true />', props: { isCool: '\n multi\n line\n', more: true, pros: true }, propsRaw: ' isCool={`\n multi\n line\n`} more pros=true ', children: [], isSelfClosing: true, position: { start: { index: 595, line: 40, column: 1 }, end: { index: 663, line: 43, column: 20 } } }, { type: 'text', content: '\n\n', position: { start: { index: 663, line: 43, column: 20 }, end: { index: 665, line: 45, column: 1 } } }, { type: 'element', tagName: 'img', tagValue: '<img src="w3schools.jpg" alt="W3Schools-img.com" width="104" height="142" />', props: { src: 'w3schools.jpg', alt: 'W3Schools-img.com', width: '104', height: '142' }, propsRaw: ' src="w3schools.jpg" alt="W3Schools-img.com" width="104" height="142" ', children: [], isSelfClosing: true, position: { start: { index: 665, line: 45, column: 1 }, end: { index: 741, line: 45, column: 76 } } }, { type: 'text', content: '\n\n', position: { start: { index: 741, line: 45, column: 76 }, end: { index: 743, line: 47, column: 1 } } }, { type: 'component', tagName: 'Bool', tagValue: '<Bool>cool</Bool>', props: {}, propsRaw: '', children: [ { type: 'text', content: 'cool', position: { start: { index: 749, line: 47, column: 6 }, end: { index: 753, line: 47, column: 10 } } } ], position: { start: { index: 743, line: 47, column: 1 }, end: { index: 760, line: 47, column: 17 } } }, { type: 'text', content: '\n', position: { start: { index: 760, line: 47, column: 17 }, end: { index: 761, line: 48, column: 1 } } } ], 'props match' ) }) test.run()