Treat generator stars like prefix operators.
See original GitHub issuePrettier 1.19.1 Playground link
--parser typescript
Input:
export function *flattenChildren(children: Children): Iterable<Child> {
if (typeof children === "string" || !isIterable(children)) {
yield children;
return;
}
for (const child of children) {
yield *flattenChildren(child);
}
}
Output:
export function* flattenChildren(children: Children): Iterable<Child> {
if (typeof children === "string" || !isIterable(children)) {
yield children;
return;
}
for (const child of children) {
yield* flattenChildren(child);
}
}
Expected behavior: Input unchanged.
This is a small nit I have with prettier, which is that it puts a space after generator stars and no space before. I believe this is suboptimal because in all cases, these stars act like a prefix operator, which prettier in all other cases puts flush against its operand (!value
, ++i
). Treating generator stars like prefix operators is both more consistent and helps catch bugs.
Consistency
There are currently two places in javascript where stars can be used to create generator functions:
- after the function keyword in function declarations and expressions:
function *fizzbuzz() {
/* ... */
}
- before method names in classes and object literals.
class FizzBuzzer {
*[Symbol.iterator]() {
/* ... */
}
}
const fizzbuzz = {
*[Symbol.iterator]() {
/* ... */
},
};
In the first case, it’s not exactly clear where the star goes and prettier places the star against the function
keyword. In the second case, there’s nothing before the star, and even prettier puts the star against the method name. Why do we treat these two cases differently? Insofar as there is no “before” to put the star against in the case of class/object method declarations, we should prefer to put the star against function names as well for the purposes of consistency.
The one instance of inconsistency which this rule causes is the case of anonymous generator expressions.
(function*() {})();
Here it would seem that putting the star against the parameter list is inconsistent because while there is a space before the star in named functions, there isn’t a space before the star in anonymous functions. I concede this point, and find that it is further evidence that we should simply add a space after all function keywords consistently (see issue #3847).
In the case of yield stars, adding a star without an expression is simply a syntax error:
yield*;
// ^ parser expects an expression
This is more evidence that generator stars should be treated like prefix operators.
Catching bugs
Function stars change the return value of functions and yield stars delegate yielding to the yielded expression. By putting these stars against the keywords function
or yield
, you increase the chance that a developer will miss that the function is a generator, or that the yield is being delegated. Programmers will often gloss over keywords like function
or yield
when reading code because they are common and unchanging, while the names of functions and the contents of yielded expressions are critically important to read and make sense of, if only to catch typos. Most syntax highlighters will also highlight stars the same color as the keyword function
and yield
, compounding the problem.
Consider this actual bug I have personally made, which cannot be type checked away:
function* getRawText(): Iterable<string> {
/* some logic to get an iterable of tokens */
for (const token of tokens) {
yield* token.getRawText();
}
}
Because strings are themselves iterables of strings (where each iteration yields a character), a type checker would not notice that the developer was accidentally yielding each token‘s text character by character. However, this is almost certainly be a bug. By placing the star flush against the expression:
yield *token.getRawText();
we make it more obvious that we are delegating to the expression, no matter how busy the expression becomes.
A similar argument can be made for function/method names. Generator functions are lazy, so it is critical that developers understand that a function returns a generator object and use the generator object in some way to execute the generator. By placing the star against the name of the function, we make it clear to readers that the function returns a generator object and executes lazily.
Possible Objections
- “Isn’t
function*
/yield*
a keyword? Doesn’t it look like a keyword?
I would argue in response that they aren’t keywords, and there isn’t a single example of a “keyword” which permits spaces between its members, or even has “members” to begin with. The keywords are function
and yield
, and while the stars modify the behavior of the keywords (they change the return value of a function or delegate yielding), they do not change the fact that we are declaring a function/yielding from a generator.
- Putting a space after stars is just established “convention.”
I would argue that there is no clear consensus about how to space generator stars, and that any “conventions” were established before generators came to be used regularly. I use generators regularly in code I write, and I have provided two objective points as to why the convention should be as I described. In addition, there is the convention of prefix operators, and I believe I’ve established that stars are more similar to prefix operators than postfix operators (even though they are neither).
Therefore, I propose prettier uniformly place generator and yield stars flush against whatever follows, rather than whatever came before. This should be the default behavior, and not an option. This can be done if/when #3847 is done.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:6
- Comments:12 (12 by maintainers)
We don’t do Twitter polls anymore. They are not really representative of the user base. I guess we just need to make sure there is no good reason for rejecting this proposal. If anybody knows an argument that can outweigh brainkim’s reasoning, let them write it now. Otherwise we’ll proceed.
I pinned this issue to bring additional attention to it.
@lipis Could you tweet a link to this issue without creating a poll?
@brainkim Would you like to make a PR if we decide to proceed?
@brainkim Okay, we’ll probably just include it in #3903, but it’s going to need a review from you.