ts-morph icon indicating copy to clipboard operation
ts-morph copied to clipboard

Weird formatting issue with prependWhitespace() for a JsxAttribute

Open gregjacobs opened this issue 5 years ago • 2 comments

Describe the bug

Version: 9.1.0

Hey @dsherret. I'm getting a weird formatting issue when trying to add some whitespace before a JsxAttribute node.

I'm trying to get a new attribute to be on its own line. So I have this (minimal example) tag:

<div
    className="my-class"
>
    Text
</div>

Adding a new attribute (align="center") with the .addAttribute() API results in the following: (attribute on same line)

<div
    className="my-class" align="center"
>
    Text
</div>

So I'm trying to prepend a line break and 4 spaces before the new 'align' attribute node (newAttr.prependWhitespace('\n    ')), but the formatting ends up like this:

<div
    className="my-class" 
        align="center"    <-- oops, 8 spaces somehow
>
    Text
</div>

If I prepend just a single line break (\n) without any spaces, I get this:

<div
    className="my-class" 
align="center"    <-- line break with no spaces
>
    Text
</div>

And if I prepend a line break and just a single space ('\n '):

<div
    className="my-class" 
     align="center"    <-- oops, 5 spaces somehow
>
    Text
</div>

I think the formatter is trying to align the new attribute vertically with the previous attribute, but then adding spaces after that.

To Reproduce

import { JsxOpeningElement, Project, SyntaxKind, ts } from 'ts-morph';

console.log(ts.version);  // 4.1.2

const project = new Project();
const sourceFile = project.createSourceFile('tmp.tsx', `
const MyComponent = () => {
    return (
        <div
            className="my-class"
        >
            Text
        </div>
    );
};
`);

const jsxEl = sourceFile.getFirstDescendantByKindOrThrow(SyntaxKind.JsxOpeningElement) as JsxOpeningElement;
const newAttr = jsxEl.addAttribute({ name: 'align', initializer: '"center"' });
newAttr.prependWhitespace('\n    ');  // somehow inserts 16 spaces in this case

console.log(sourceFile.getFullText());

Output from this particular example:

const MyComponent = () => {
    return (
        <div
            className="my-class" 
                align="center"    // <-- 16 spaces
        >
            Text
        </div>
    );
};

Expected behavior

I would have expected the line break and the exact 4 spaces I put in to be present after I call prependWhitespace('\n    ').

However, if there's some formatting that's supposed to go on here, it seems that maybe it should trigger from just adding a \n char?

gregjacobs avatar Dec 08 '20 05:12 gregjacobs

Hmmm... that should be fixed. That said, you should check out dprint 😅

dsherret avatar Dec 11 '20 03:12 dsherret

Very cool project @dsherret! And written in Rust too, how fun :)

Unfortunately in this case I can't run a formatter though. The tool we're writing is a codemod tool for users of our React library, and I don't want to reformat the full source file if we're just making a small edit to say, a single JSX element. Running a formatter could create a lot of noise in the diffs, and we may also accidentally reformat it in conflict with their project's standards.

Do you think fixing this is a quick thing or kind of involved? If you can direct me to the source files I might need to touch, I could submit a PR 👍

Best, Greg

gregjacobs avatar Dec 11 '20 18:12 gregjacobs