Skip to content

Commit

Permalink
feat(pretty): prettify objects, arrays, nested
Browse files Browse the repository at this point in the history
BREAKING CHANGE: objects and arrays are now prettified by default following #50 
If this is a concern to you, open a PR that adds an option to inline parts or the whole output like before
  • Loading branch information
beauroberts authored and vvo committed Oct 24, 2016
1 parent 3641986 commit 864b9db
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 46 deletions.
122 changes: 97 additions & 25 deletions index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,27 @@ describe('reactElementToJSXString(ReactElement)', () => {
it('reactElementToJSXString(<div obj={{hello: \'world\'}}/>)', () => {
expect(
reactElementToJSXString(<div obj={{hello: 'world'}}/>)
).toEqual('<div obj={{hello: \'world\'}} />');
).toEqual(`<div
obj={{
hello: 'world'
}}
/>`);
});

it('reactElementToJSXString(<div obj={{hello: [1, 2], world: {nested: true}}}/>)', () => {
expect(
reactElementToJSXString(<div obj={{hello: [1, 2], world: {nested: true}}}/>)
).toEqual('<div obj={{hello: [1, 2], world: {nested: true}}} />');
).toEqual(`<div
obj={{
hello: [
1,
2
],
world: {
nested: true
}
}}
/>`);
});

it('reactElementToJSXString(<div></div>)', () => {
Expand Down Expand Up @@ -212,6 +226,32 @@ describe('reactElementToJSXString(ReactElement)', () => {
</div>`);
});

it('reactElementToJSXString(<div a={{a: "1", b: {c: "3"}}}><div b={{c: {d: "4"}}}>Hello</div></div>)', () => {
expect(
reactElementToJSXString(
<div a={{a: '1', b: {c: '3'}}}><div b={{c: {d: '4'}}}>Hello</div></div>,
)
).toEqual(
`<div
a={{
a: '1',
b: {
c: '3'
}
}}
>
<div
b={{
c: {
d: '4'
}
}}
>
Hello
</div>
</div>`);
});

it('reactElementToJSXString()', () => {
expect(() => {
reactElementToJSXString();
Expand Down Expand Up @@ -251,19 +291,36 @@ describe('reactElementToJSXString(ReactElement)', () => {
it('reactElementToJSXString(<div a={{b: function hello() {}}} />', () => {
expect(
reactElementToJSXString(<div a={{b: function hello() {}}} />)
).toEqual('<div a={{b: function noRefCheck() {}}} />');
).toEqual(`<div
a={{
b: function noRefCheck() {}
}}
/>`);
});

it('reactElementToJSXString(<div a={{b: {c: {d: <div />, e: null}}}} />', () => {
expect(
reactElementToJSXString(<div a={{b: {c: {d: <div />, e: null}}}} />)
).toEqual('<div a={{b: {c: {d: <div />, e: null}}}} />');
).toEqual(`<div
a={{
b: {
c: {
d: <div />,
e: null
}
}
}}
/>`);
});

it('reactElementToJSXString(<div a={{b: {}}} />', () => {
expect(
reactElementToJSXString(<div a={{b: {}}} />)
).toEqual('<div a={{b: {}}} />');
).toEqual(`<div
a={{
b: {}
}}
/>`);
});

it('reactElementToJSXString(<div a={{}} />', () => {
Expand Down Expand Up @@ -293,25 +350,36 @@ describe('reactElementToJSXString(ReactElement)', () => {
it('reactElementToJSXString(<div a={[1, 2, 3, 4]} />', () => {
expect(
reactElementToJSXString(<div a={[1, 2, 3, 4]} />)
).toEqual('<div a={[1, 2, 3, 4]} />');
).toEqual(`<div
a={[
1,
2,
3,
4
]}
/>`);
});

it('reactElementToJSXString(<div a={[1, 2, 3, 4]} />', () => {
it('reactElementToJSXString(<div a={[{Hello: \', world!\'}]} />)', () => {
expect(
reactElementToJSXString(<div a={[{Hello: ', world!'}]} />)
).toEqual('<div a={[{Hello: \', world!\'}]} />');
).toEqual(`<div
a={[
{
Hello: ', world!'
}
]}
/>`);
});

it('reactElementToJSXString(<div a={[{}]} />', () => {
expect(
reactElementToJSXString(<div a={[{}]} />)
).toEqual('<div a={[{}]} />');
});

it('reactElementToJSXString(<div a={[]} />', () => {
expect(
reactElementToJSXString(<div a={[]} />)
).toEqual('<div a={[]} />');
).toEqual(`<div
a={[
{}
]}
/>`);
});

it('reactElementToJSXString(<div a={[]} />', () => {
Expand All @@ -323,7 +391,11 @@ describe('reactElementToJSXString(ReactElement)', () => {
it('reactElementToJSXString(<div a={[<div key="0"><span /></div>]} />', () => {
expect(
reactElementToJSXString(<div a={[<div key="0"><span /></div>]} />)
).toEqual('<div a={[<div key="0"><span /></div>]} />');
).toEqual(`<div
a={[
<div key="0"><span /></div>
]}
/>`);
});

it('reactElementToJSXString(decorator(<span />)', () => {
Expand Down Expand Up @@ -368,12 +440,6 @@ describe('reactElementToJSXString(ReactElement)', () => {
</div>`);
});

it('reactElementToJSXString(<div a={[<div key="0"><span /></div>]} />', () => {
expect(
reactElementToJSXString(<div a={[<div key="0"><span /></div>]} />)
).toEqual('<div a={[<div key="0"><span /></div>]} />');
});

it('reactElementToJSXString(<div aprop="test" ref="yes" />', () => {
expect(
reactElementToJSXString(<div aprop="test" ref="yes" />)
Expand Down Expand Up @@ -540,7 +606,11 @@ describe('reactElementToJSXString(ReactElement)', () => {
reactElementToJSXString(<div co={{a: <div a="1" />}} />, {
displayName: element => element.type.toUpperCase(),
})
).toEqual('<DIV co={{a: <DIV a="1" />}} />');
).toEqual(`<DIV
co={{
a: <DIV a="1" />
}}
/>`);
});
it('should omit true as value', () => {
expect(
Expand All @@ -556,9 +626,11 @@ describe('reactElementToJSXString(ReactElement)', () => {
it('should return the actual functions when "showFunctions" is true', () => {
expect(
reactElementToJSXString(<div fn={() => 'value'}/>, {showFunctions: true})
).toEqual(`<div fn={function fn() {
).toEqual(`<div
fn={function fn() {
return 'value';
}} />`);
}}
/>`);
});

it('reactElementToJSXString(<DisplayNamePrecedence />)', () => {
Expand Down
78 changes: 57 additions & 21 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,37 @@ got \`${typeof Element}\``
const tagName = getDisplayName(Element);

let out = `<${tagName}`;
const props = formatProps(Element.props, getDefaultProps(Element));
const props = formatProps(Element.props, getDefaultProps(Element), inline, lvl);
let attributes = [];
const children = React.Children.toArray(Element.props.children)
.filter(onlyMeaningfulChildren);

if (Element.ref !== null) {
attributes.push(getJSXAttribute('ref', Element.ref));
attributes.push(getJSXAttribute('ref', Element.ref, inline, lvl));
}

if (Element.key !== null &&
// React automatically add key=".X" when there are some children
!(/^\./).test(Element.key)) {
attributes.push(getJSXAttribute('key', Element.key));
attributes.push(getJSXAttribute('key', Element.key, inline, lvl));
}

attributes = attributes
.concat(props)
.filter(({name}) => filterProps.indexOf(name) === -1);

let containsMultilineAttr = false;
attributes.forEach(attribute => {
if (attributes.length === 1 || inline) {
let isMultilineAttr = false;
if (['plainObject', 'array', 'function'].indexOf(attribute.type) > -1) {
isMultilineAttr = attribute.value.indexOf('\n') > -1;
}

if (isMultilineAttr) {
containsMultilineAttr = true;
}

if ((attributes.length === 1 || inline) && !isMultilineAttr) {
out += ' ';
} else {
out += `\n${spacer(lvl + 1, tabStop)}`;
Expand All @@ -67,7 +77,7 @@ got \`${typeof Element}\``
}
});

if (attributes.length > 1 && !inline) {
if ((attributes.length > 1 || containsMultilineAttr) && !inline) {
out += `\n${spacer(lvl, tabStop)}`;
}

Expand Down Expand Up @@ -104,7 +114,7 @@ got \`${typeof Element}\``
return out;
}

function formatProps(props, defaultProps) {
function formatProps(props, defaultProps, inline, lvl) {
let formatted = Object
.keys(props)
.filter(noChildren);
Expand All @@ -119,27 +129,44 @@ got \`${typeof Element}\``

return formatted
.sort()
.map(propName => getJSXAttribute(propName, props[propName]));
.map(propName => getJSXAttribute(propName, props[propName], inline, lvl));
}

function getJSXAttribute(name, value) {
function getJSXAttribute(name, value, inline, lvl) {
return {
name,
value: formatJSXAttribute(value)
type: getValueType(value),
value: formatJSXAttribute(value, inline, lvl)
.replace(/'?<__reactElementToJSXString__Wrapper__>/g, '')
.replace(/<\/__reactElementToJSXString__Wrapper__>'?/g, ''),
};
}

function formatJSXAttribute(propValue) {
function formatJSXAttribute(propValue, inline, lvl) {
if (typeof propValue === 'string') {
return `"${propValue}"`;
}

return `{${formatValue(propValue)}}`;
return `{${formatValue(propValue, inline, lvl)}}`;
}

function formatValue(value) {
function getValueType(value) {
if (isElement(value)) {
return 'element';
}

if (isPlainObject(value)) {
return 'plainObject';
}

if (Array.isArray(value)) {
return 'array';
}

return typeof value;
}

function formatValue(value, inline, lvl) {
const wrapper = '__reactElementToJSXString__Wrapper__';

if (typeof value === 'function' && !showFunctions) {
Expand All @@ -154,7 +181,7 @@ got \`${typeof Element}\``
// otherwise, the element would be surrounded by quotes: <div a={{b: '<div />'}} />
return `<${wrapper}>${toJSXString({ReactElement: value, inline: true})}</${wrapper}>`;
} else if (isPlainObject(value) || Array.isArray(value)) {
return `<${wrapper}>${stringifyObject(value)}</${wrapper}>`;
return `<${wrapper}>${stringifyObject(value, inline, lvl)}</${wrapper}>`;
}

return value;
Expand All @@ -164,23 +191,32 @@ got \`${typeof Element}\``
return Element => toJSXString({ReactElement: Element, lvl, inline});
}

function stringifyObject(obj) {
function stringifyObject(obj, inline, lvl) {
if (Object.keys(obj).length > 0 || obj.length > 0) {
// eslint-disable-next-line array-callback-return
obj = traverse(obj).map(function(value) {
if (isElement(value) || this.isLeaf) {
this.update(formatValue(value));
this.update(formatValue(value, inline, lvl));
}
});

obj = sortobject(obj);
}

return collapse(stringify(obj))
.replace(/{ /g, '{')
.replace(/ }/g, '}')
.replace(/\[ /g, '[')
.replace(/ \]/g, ']');
const stringified = stringify(obj);

if (inline) {
return collapse(stringified)
.replace(/{ /g, '{')
.replace(/ }/g, '}')
.replace(/\[ /g, '[')
.replace(/ \]/g, ']');
}

// Replace tabs with spaces, and add necessary indentation in front of each new line
return stringified
.replace(/\t/g, spacer(1, tabStop))
.replace(/\n([^$])/g, `\n${spacer(lvl + 1, tabStop)}$1`);
}
}

Expand Down Expand Up @@ -213,7 +249,7 @@ function mergePlainStringChildren(prev, cur) {
}

function spacer(times, tabStop) {
return fill(new Array(times * tabStop), ' ').join('');
return times === 0 ? '' : fill(new Array(times * tabStop), ' ').join('');
}

function noChildren(propName) {
Expand Down

0 comments on commit 864b9db

Please sign in to comment.