-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
Linebreaks weirdly in long ES6 template strings with expressions #3368
Comments
Ref: previous discussion #3315 (comment) |
As we'll focus the discussion on this issue, I'll copy my comment from the other thread (cc @lipis): The thing about template literals is without breaking between const str1 = `text text text ${foo.
bar} text text`;
const str2 = `text text text ${foo[
bar
]} text text`; which IMO are strictly worse than what we're printing right now. Note the The other problem with this is when should and shouldn't break in those cases: foo = `Text text text text text ${foo.bar.baz.spam.eggs}`;
foo = `Text text text text text ${longFooBarBazSpam.longFooBarBazEggs}`; Until we can find an heurisitic that's good enough to decide on those cases, it's better to keep consistent that always breaks. |
I think those cases we shouldn't brake at all.. foo = `Text text text text text ${foo.bar.baz.spam.eggs}`;
foo = `Text text text text text ${longFooBarBazSpam.longFooBarBazEggs}`; and consider that a single expression while this one is not: foo = `Text text text text text ${foo.bar.baz.spam.eggs + foo.bar.baz.bacon}`;
foo = `Text text text text text ${longFooBarBazSpam.longFooBarBazEggs - longFooBarBazSpam.longFooBarBazBacon}`; |
Personally I am of the opinion that a template string, regardless of what’s
in there, is a single expression. People putting too much logic in there is
an antipattern and almost always leads to a mess.
Discouraging messes is the job of a linter, not a formatter, if I’m not
mistaken. Maybe a good way to approach this is to have people vote on
whether they perceive complex string templates as one expression or more?
…On Wed, Dec 6, 2017 at 6:33 AM Lipis ***@***.***> wrote:
I think those cases we shouldn't brake at all..
foo = `Text text text text text ${foo.bar.baz.spam.eggs}`;
foo = `Text text text text text ${longFooBarBazSpam.longFooBarBazEggs}`;
and consider that a *single expression*
while this one is not:
foo = `Text text text text text ${foo.bar.baz.spam.eggs + foo.bar.baz.bacon}`;
foo = `Text text text text text ${longFooBarBazSpam.longFooBarBazEggs - longFooBarBazSpam.longFooBarBazBacon}`;
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#3368 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AADeM-Fq6oU30YxsD1pd5H5hrhiYl-dSks5s9nujgaJpZM4Qx-k7>
.
|
I would agree.. maybe it makes sense to not break them at all.. |
The job of a formatter is to format the code, whether or not it's a mess, trying its best to output code that looks consistent. I don't think we should be in charge of encouraging or discouraging any pattern. Don't get me wrong, I would love if people wrote code in specific styles.. that would make the job of a formatter a whole lot simpler, but that's not what we've noticed out there in codebases, both smaller and bigger ones. As for the formatting issue, prettier used to not format template literals at all, but people complained code wasn't getting formatted. Then it started formatting but using I see what we have today as a good compromise but I agree "simple" cases could get better. The whole point is deciding what is simple and have a sane way to format those cases without adding too much complexity to the code. See #2046, #1893, #1662, #1626, #821, #1183 (and there's more :-)) for more context on this issue. |
If there's a rule of thumb needed, I'd propose that inserting one existing value into a template string should never cause breaks. If you're doing something that could create a new value (getters aside), or picks between values, it's complex enough to warrant breaks. Expressions that shouldn't break: `${foo}` // this is done, which is awesome!
`${foo.bar}`
`${foo[bar]}`
`${foo[bar][baz]}` There's some interesting middle ground that would be nice to have, but breaks with that rule of thumb: `${foo()}` // all of the above as function calls with no arguments. i.e. just 'get this computed value'
`${foo || bar}` // to inline defaults without paying the 'break cost'. but with member access chains, this could get long. :shrug: |
I'd say a lot of people, including myself, could dig an option that reverts to previous behaviour i.e. rule to disable formatting template literals completely e.g. ignoreTemplateLiterals: true. Does this exist already? Any reason this would be a problem? |
I definitely think that template literals should be treated as one holistic expressions and their contents should never be altered. A travesty of formattinglog(
chalk.bgRed(
chalk.white(
`Covered Lines below threshold: ${
coverageSettings.lines
}%. Actual: ${coverageSummary.total.lines.pct}%`
)
)
); (Github displaying tabs as 8 spaces is also a travesty) |
@JonathanWolfe Would you agree that this should not be formatted? `Covered Lines below threshold: ${
coverageSettings
.lines
}%. Actual: ${ coverageSummary
.total.
lines
.
pct
}%` (that’s valid JS) |
@j-f1: Unfortunately, and it pains me to say this to that example, yes; I would leave that alone. The copout answer is because nobody would write something like that, but really, I believe the contents of Template Strings/Literals/Expressions to be intentionally formatted by the author when they're written. An ideal answer might be to leave the expression location alone, but format them as if it is a standalone line. This way the contents get formatted and made pretty, but the braces aren't broken apart. Maybe an even simpler answer is to not allow line breaks when formatting template expressions. |
@JonathanWolfe We used to not format and we changed it because a LOT of people expected the code to be formatted there as well. So it's not an option to stop formatting those expressions. We do want to make the formatting better and we'll gladly take suggestions |
@duailibe: I get that expectation. I would say the best thing to do would be to not linebreak the braces of |
Yeah that's an option.. If we simply remove the linebreaks, that would be formatted as: const foo = `some text here ${foo
.bar.baz} continues here`; Which is worse than what we're doing right now. That's just how it prints a const foo = `some text here ${foo.something({
key: prop
}).bar.baz()}`; Should it remain as is? That's inconsistent with how we print that case in other places: foo
.something({
key: prop
})
.bar.baz(); Anyway, we're not opposed on improving that, it's just it's a difficult problem and we'd need a consistent suggestion of different examples and how we should print them. Hopefully that gives more context on the decisions on this subject. |
@duailibe: Sure, I've got lots of time right now so I'll make a big list of before/afters. I went out and took a bunch of template literals from MDN, CSS-Tricks, and the lit-html repo. It appears to me that the heuristic to use to get the best formatting is:
// Input
console.log(`Fifteen is ${a + b} and not ${2*a +
b}.`);
// Current
console.log(`Fifteen is ${a + b} and not ${2 * a + b}.`);
// Desired - No change
console.log(`Fifteen is ${a + b} and not ${2 * a + b}.`); // Input
const foo = `some text here ${foo.something({
key: prop
}).bar.baz()}`;
// Current
const foo = `some text here ${foo
.something({
key: prop,
})
.bar.baz()}`;
// Desired
const foo = `some text here ${foo.something({key: prop}).bar.baz()}` // Input
const classes = `header ${ isLargeScreen() ? '' :
(item.isCollapsed ? 'icon-expander' : 'icon-collapser') }`;
const classes = `header ${ isLargeScreen() ? '' :
`icon-${(item.isCollapsed ? 'expander' : 'collapser')}` }`;
// Current
const classes = `header ${
isLargeScreen() ? '' : item.isCollapsed ? 'icon-expander' : 'icon-collapser'
}`;
const classes = `header ${
isLargeScreen() ? '' : `icon-${item.isCollapsed ? 'expander' : 'collapser'}`
}`;
// Desired
const classes = `header ${isLargeScreen() ? '' : item.isCollapsed ? 'icon-expander' : 'icon-collapser'}`;
const classes = `header ${isLargeScreen() ? '' : `icon-${item.isCollapsed ? 'expander' : 'collapser'}`}`; // Input
var pronoun = person.parent.getPronoun({formal:true});
var output = myTag`that ${person.parent.getPronoun({ formal:true })} is a ${ person.parent.getAge() }`;
// Current
var pronoun = person.parent.getPronoun({ formal: true });
var output = myTag`that ${person.parent.getPronoun({
formal: true,
})} is a ${person.parent.getAge()}`;
// Desired
var pronoun = person.parent.getPronoun({ formal: true });
var output = myTag`that ${person.parent.getPronoun({ formal: true })} is a ${person.parent.getAge()}`; // Input
let person = {
firstName: `Ryan`,
lastName: `Christiani`,
sayName() {
return `Hi my name is ${this.firstName} ${this.lastName}`;
}
};
// Current
let person = {
firstName: `Ryan`,
lastName: `Christiani`,
sayName() {
return `Hi my name is ${this.firstName} ${this.lastName}`;
},
};
// Desired - No change
let person = {
firstName: `Ryan`,
lastName: `Christiani`,
sayName() {
return `Hi my name is ${this.firstName} ${this.lastName}`;
},
}; // Input
function createMarkup(data) {
return `
<article class="pokemon">
<h3>${data.name}</h3>
<p>The Pokemon ${data.name} has a base experience of ${data.base_experience}, they also weigh ${data.weight}</p>
</article>
`
}
// Current
function createMarkup(data) {
return `
<article class="pokemon">
<h3>${data.name}</h3>
<p>The Pokemon ${data.name} has a base experience of ${
data.base_experience
}, they also weigh ${data.weight}</p>
</article>
`;
}
// Desired
function createMarkup(data) {
return `
<article class="pokemon">
<h3>${data.name}</h3>
<p>The Pokemon ${data.name} has a base experience of ${data.base_experience}, they also weigh ${data.weight}</p>
</article>
`;
} // Input
class element {
render() {
return svg`<g>
${[0, 10, 20].map((x, i) => svg`<line x1=${x * i} y1="0" x2=${x * i} y2="20"/>`)}
${[0, 10, 20].map((y, i) => svg`<line x1="0" y1=${y + i} x2="0" y2=${y + i}/>`)}
</g>`;
}
}
// Current
class element {
render() {
return svg`<g>
${[0, 10, 20].map(
(x, i) => svg`<line x1=${x * i} y1="0" x2=${x * i} y2="20"/>`
)}
${[0, 10, 20].map(
(y, i) => svg`<line x1="0" y1=${y + i} x2="0" y2=${y + i}/>`
)}
</g>`;
}
}
// Desired - no change
class element {
render() {
return svg`<g>
${[0, 10, 20].map(
(x, i) => svg`<line x1=${x * i} y1="0" x2=${x * i} y2="20"/>`
)}
${[0, 10, 20].map(
(y, i) => svg`<line x1="0" y1=${y + i} x2="0" y2=${y + i}/>`
)}
</g>`;
}
} // Input
const render = () => html`
<ul>
${repeat(items, (i) => i.id, (i, index) => html`
<li>${index}: ${i.name}</li>`)}
</ul>
`;
// Current
const render = () => html`
<ul>
${repeat(
items,
i => i.id,
(i, index) => html`
<li>${index}: ${i.name}</li>`
)}
</ul>
`;
// Desired - no change
const render = () => html`
<ul>
${repeat(
items,
i => i.id,
(i, index) => html`
<li>${index}: ${i.name}</li>`
)}
</ul>
`; // Input
const render = () => html`
${when(state === 'loading',
html`<div>Loading...</div>`,
html`<p>${message}</p>`)}
`;
// Current
const render = () => html`
${when(
state === 'loading',
html`<div>Loading...</div>`,
html`<p>${message}</p>`
)}
`;
// Desired - No change
const render = () => html`
${when(
state === 'loading',
html`<div>Loading...</div>`,
html`<p>${message}</p>`
)}
`; // Input
const render = () => html`
<div>Current User: ${guard(user, () => user.getProfile())}</div>
`;
// Current
const render = () => html`
<div>Current User: ${guard(user, () => user.getProfile())}</div>
`;
// Desired
const render = () => html`
<div>Current User: ${guard(
user,
() => user.getProfile()
)}</div>
`; // Input
const omg = `Covered Lines below threshold: ${
coverageSettings
.lines
}%. Actual: ${ coverageSummary
.total.
lines
.
pct
}%`
// Current
const omg = `Covered Lines below threshold: ${
coverageSettings.lines
}%. Actual: ${coverageSummary.total.lines.pct}%`;
// Desired
const omg = `Covered Lines below threshold: ${coverageSettings.lines}%. Actual: ${coverageSummary.total.lines.pct}%`; // Input
log(
chalk.bgRed(
chalk.white(
`Covered Lines below threshold: ${coverageSettings.lines}%. Actual: ${coverageSummary.total.lines.pct}%`
)
)
);
// Current
log(
chalk.bgRed(
chalk.white(
`Covered Lines below threshold: ${
coverageSettings.lines
}%. Actual: ${coverageSummary.total.lines.pct}%`
)
)
);
// Desired
log(
chalk.bgRed(
chalk.white(
`Covered Lines below threshold: ${coverageSettings.lines}%. Actual: ${coverageSummary.total.lines.pct}%`
)
)
); // Input
render(html`
Count: <span>${asyncReplace(countUp())}</span>.
`, document.body);
// Current
render(
html`
Count: <span>${asyncReplace(countUp())}</span>.
`,
document.body
);
// Desired - No change
render(
html`
Count: <span>${asyncReplace(countUp())}</span>.
`,
document.body
); |
It's been a month since @JonathanWolfe's suggestion and a few thumbsups on the post. How do we keep it moving? Someone want to do a PR? |
I just started using prettier and to immediately turn it off because of this! Definitely willing to help out if necessary! |
*tumbleweeds and crickets* Would a PR for this be welcomed? |
A PR exploring ways to improve the oh-so-tricky-to-format template literals would certainly be up for discussion 👍 |
I think with template literal, it shouldn't try to respect |
Templates strings are still string, and it's helpful to display them as is, hence never ever add a new line. If you need to call a function in a template string, then just declare a variable. |
most of the time I log, I apply format functions to the content: console.log(`... ${format(x)} ...`) which ends up as console.log(`...${
format(x)
}...`); undesirable, IMO. I'd rather have template literals left untouched. It should probably be an option: {
singleQuotes: true,
...
ignoreTemplateLiterals: true,
} |
@doplumi If you have a line length of |
@nicu-chiciuc you see what they meant, I don't think they were saying it's a problem with a line length of 35 |
It's difficult to follow all of these posts. What is the behavior for the current version of prettier and are there options to override it? In my case I have a print width of 120 and would like reformatting only of lines that exceed this length and contain a legit ${...}. Prettier seems to be changing every template literal in my .js files to be on a single line regardless of the length (it does respect the backslashes at the end of lines however). |
@terrisgit can you open a new issue by pasting some sample code into the playground, making sure that it demonstrates the undesired behavior you’re seeing, and clicking the “report issue” button at the bottom right? It’s hard to figure out exactly what’s causing the issue unless we can see in detail what’s happening. |
@j-f1 The playground link in the first post is pretty good. It doesn't work though.
|
With simple template strings seems now works well, but if template contains function - Prettier breaks it to several string, here is modified previous example: String with template, containing function Is this fixable with current version using some configuration options, or maybe some workaround exists? |
Sadly, despite the "great demand" from the community, the PR #12505 isn't approved. This is still one thing from Prettier that bugs me. Template literals are just that: literals. If a person doesn't intend to add a newline in a template literal, then, maybe, it should look like there's no new line, no? Or maybe at least give us a choice. Forcing to use concatenation instead of template literals for really long string also isn't always readable. Despite what you said in the Option Philosophy, I just can hope you'd consider reopening that PR 🙏 |
Just wanted to chime in here with more support for adding this option. I am also generally in the camp of less options are better, just give me a reasonable format and don't make me think about it, which is why I love prettier! But the line splitting on template literals really drives me nuts. It almost always makes the code harder to read and understand. I would really appreciate an option for this behavior! |
Found this discussion because I would also like to see this option. |
Yeah, put my in the camp of adding configuration for this. I really dislike the way this is handled. |
Just adding my name to the people who expect better handling of template literals. |
Adding my name here too. |
OMG, I think many people including me still feel pain from the backtick problem. Shouldn't Prettier make code prettier? |
Here's a heuristic I haven't seen proposed which I think would make ~everyone happy: Only add linebreaks to an interpolation if there's already a linebreak within the interpolation. This is similar to how an object expression will get put on multiple lines only if it exceeds print width or contains linebreaks, just with an "and" instead of an "or". That way if you have a complex interpolation which you want prettier to break up for you, you can put a linebreak in. And if you don't want it broken up - for example, if you wrote the whole template on one line - then prettier will leave it alone. Thoughts? I'm happy to try a PR if this sounds good. |
In version 3.2.0, Prettier incorporates the heuristic I suggested in my previous comment: it will no longer add linebreaks to interpolations in template literals unless there is already one present. So if you want to keep the whole string on a single line, just write it that way, and Prettier will respect that. (It will still format the interpolations as normal, just effectively with infinite This won't make everyone happy, but I hope it's an improvement for most people in this thread. |
That's a simple yet amazing improvement. Thank you for your work |
I wonder if this could be closed since #15209 has been merged. The template literal formatting is great now. |
@Lehoczky-peakfs, yep, makes sense, thanks! Here is how Prettier 3.3 formats the initial example: Prettier 3.3.0 --parser babel
--trailing-comma none
--prose-wrap always Input: class Quote {
character = {
name: 'Dorian'
}
text = `Yes, ${this.character.name}, you will always be fond of me. I represent to you all the sins you have never had the courage to commit.`
} Output: class Quote {
character = {
name: "Dorian"
};
text = `Yes, ${this.character.name}, you will always be fond of me. I represent to you all the sins you have never had the courage to commit.`;
} |
Prettier 1.8.2
Playground link
# Options (if any): --print-width=80
Input:
Output:
Expected behavior:
I would prefer a long string to not get split into multiple lines, even if it contains expressions. When there are no expressions, Prettier correctly lets a long string go beyond the
print-width
limit.I can't decide if this is a bug or a personal preference, but it's been creating weird hard to read code in our codebase. We can clean most of it up by simplifying our expressions, but there's only so short we can go.
The text was updated successfully, but these errors were encountered: