Skip to content

Treat generator stars like prefix operators. #7028

@brainkim

Description

@brainkim

Prettier 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:

  1. after the function keyword in function declarations and expressions:
function *fizzbuzz() {
  /* ... */
}
  1. 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

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    lang:javascriptIssues affecting JSlocked-due-to-inactivityPlease open a new issue and fill out the template instead of commenting.status:needs discussionIssues needing discussion and a decision to be made before action can be taken

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions