dmnd/dedent

Leading whitespace should not be stripped from interpolated components

wchargin opened this issue · 2 comments

As currently written, this module performs interpolation before
stripping leading whitespace. In particular, whitespace is stripped from
the interpolated components. I think that this is the wrong behavior.

My understanding is that dedent is supposed to be effectively
syntactic sugar. Writing

function foo() {
  const things = dedent`\
    lorem ipsum
    dolor sit amet
  `;
  return things;
}

should be completely equivalent to writing

function foo() {
  const things = `\
lorem ipsum
dolor sit amet
`;
  return things;
}

with the sole benefit that the code indentation doesn’t have to become
wonky just to accommodate the text.

This suggests that

function foo() {
  const stuff = "abc\n    xyz";
  const things = dedent`\
    lorem ${stuff}
    dolor sit amet
  `;
  return things;
}

should also be equivalent to

function foo() {
  const stuff = "abc\n    xyz";
  const things = `\
lorem ${stuff}
dolor sit amet
`;
  return things;
}

which means that the output should be

lorem abc
    xyz
dolor sit amet

but with the current version of dedent the result is actually

lorem abc
xyz
dolor sit amet

Let me provide a concrete case where this matters. I was using dedent
to server-side render static pages on my site. The code looked roughly
like this:

            // lots of context, so this code is indented a bunch...
            const page = dedent`\
                <!DOCTYPE html>
                <html>
                <body>
                <div id="container">${html}</div>
                <script src="${pathToBundle}"></script>
                </body>
                </html>
                `;

This all worked well—I wrote it and forgot about it. But when I added
code blocks to my site, I noticed that they rendered incorrectly: code
like

<pre><code>function foo(bar, baz) {
    while (true) {
        if (bar()) {
            console.log("stuff");
            if (baz()) {
                console.log(
                    "even more stuff"
                );
            }
        }
    }
}</code></pre>

was being rendered as

<pre><code>function foo(bar, baz) {
while (true) {
if (bar()) {
console.log("stuff");
if (baz()) {
console.log(
    "even more stuff"
);
}
}
}
}</code></pre>

in the static HTML. Because pre sets white-space: pre, the resulting
code is rendered incorrectly in the browser, too. This was somewhat
difficult to track down!

I ended up replacing this module with a hand-written version that fit my
purposes better. (There was an additional change that I needed to make
to properly render code blocks with line-trailing backslashes.) You can
see that change here:

wchargin/wchargin.github.io@06475d4

Is this something that you’re interested in pulling upstream? I realize
that it is a breaking change, so I’d understand if you’re not
interested.

(Also, 👋 !)

Note that your function fails some tests:

it("should do nothing for escaped newlines in template tag", () => {
  const text = dedent`${"one "}\
    two
    three
  `;
  assertEquals(text, "one two\nthree\n");
});

The indent on the second line is never removed. Also the first line isn't handled specially.

This is why reading raw is the correct approach.

I think this is actually a duplicate of #12. Moving discussion there. Please let me know if that's wrong though!