tc39/proposal-decorators

Whitespace within/after `@init:`

js-choi opened this issue · 15 comments

Spinning this out of #430:

The specification currently treats @init: as one token. While it is probably good to prohibit whitespace between @ and init (see #430), it is not so clear that whitespace should be prohibited between init and :. @ init: foo class C {} is weird, but @init : foo class C {} seems less weird—after all, @init:foo class C {} will be allowed anyway.

I think that we should consider breaking @init: up into @init : (or perhaps @ [contiguous] init :, using the [contiguous] annotation proposed in #430).

I think the explicit intention is that the @init, the :, and the expression be tightly linked - it makes it seem less like a label if there's no whitespace permitted.

I think that we should take every opportunity we can to obviate whitespace debates and relieve the linting community of the need to legislate things.

I think the explicit intention is that the @init, the :, and the expression be tightly linked

In that case, what we could do instead is to prohibit whitespace between @init: and the following decorator expression.

That is, this:

@init:foo class C {}

…is allowed, but these are prohibited:

@init: foo class C {}
@init:/* comment */foo class C {}
@init:
foo class C {}

In other words, we could change these productions:

  • Decorator : @init: DecoratorMemberExpression
  • Decorator : @init: DecoratorCallExpression

…into this:

  • Decorator : @init: [contiguous] DecoratorMemberExpression
  • Decorator : @init: [contiguous] DecoratorCallExpression

That way, the whitespace rules for @foo and @init:foo would truly be consistent.

@bakkot left a comment in tc39/ecmarkup#356 (comment) about an alternate approach:

That said, I'm not convinced [a new [contiguous] annotation] is the right approach to solve the problems you've highlighted.

For decorators, we ought to be able to just define a Decorator token along the lines of @IdentifierName. This would be a single token, like PrivateName, and would therefore not allow whitespace.

But does that approach resolve this issue? You could certainly define @init: to be a new token, and thus prevent whitespace within it, but that won't prevent whitespace between the colon and what follows (as proposed above). If you try to extend the approach to cover that gap, you end up defining @init:( and @init:IdentifierName as tokens, which I think would cause problems.

One thing I am wary of, is init the only "flag" decorators will ever need? And could there be other extensions that need syntax is this position?

If there are multiple additional "flags" ever added, things like ordering will need to considered, if many many additional "flags" are added then there will be a point where people will want to start breaking them up into lines or groups.

Allowing no spaces at all is a fairly safe start as it could be removed easily, but it should be considered if the choice of @init: syntax itself will limit or make any future extensions (such as new "flags") difficult or hacky.

No, multiples under the same @ will never be a thing; you'd have to chain them to do that (@init:x @yogurt:y z)

No, multiples under the same @ will never be a thing; you'd have to chain them to do that (@init:x @yogurt:y z)

That seems complicated if there's additional methods like .addInitializer behind "flags", we don't really want a situation where you need to duplicate decorators just to have additional things available within the decorator, especially if they otherwise could be composable just fine.

To be clear, I don't think there's likely to be any additional decorator modifiers. The intention of @init: is to say "this decorator has extra capabilities"; there's no reason they'd need composition imo.

The intention of @init: is to say "this decorator has extra capabilities"; there's no reason they'd need composition imo.

The thing is this is hard to anticipate, I mean I wouldn't've expected @init: to be neccessary for .addInitializer, but because of engine concerns about the cost, here we are. If there's any such future ideas that turn into proposals we don't want syntax that isn't extensible.

Also shoving arbitrary "extra capabilities" behind @init: doesn't make sense if they have nothing to do with "initialization". The point of @init: is it adds "intilization" capabilities, not just anything.

As an example of a syntax space where extension is fairly natural would be class modifiers, e.g. static get, the extension space for additional modifiers (e.g. enumerable) is fairly obvious.


e.g. As an example suppose there was a future extension that allowed decorators to do async initialization or something (i.e. maybe they need to import a module, or fetch something, or inject a service asynchronously, or whatever). If this was the case I'd imagine an additional modifier (e.g. @await:) would be required, but this is orthogonal to @init, whether an instance initializer is needed is unrelated to whether async is needed. As such there'd be plenty of cases where both would be combined together.

I'm not neccessarily saying this idea is sufficiently useful that it should be considered as a real proposal, but it's an example of a possible and at least somewhat desirable extension (for some use cases) where the syntax space is shared.


I don't think anyone can reasonably assume to know a-priori of all future ideas/extensions that might be desirable to add to decorators, so I think it's important to ensure this syntax space is considered carefully enough that @init: doesn't block, or forces awkward hacks for, any future extensions.

Certainly I think it would be very bad if future extensions were blocked in future simply because @init: was already in that syntax space. Given decorators is one of the most desired proposals by a very wide range of developers, it would be highly unfortunate if decisions now had significant negative impact later on making decorators as maximally useful to developers as it can be.

@Jamesernator FWIW this discussion seems off topic for this particular issue, but these issues have been considered thoughtfully as part of the design process as it stands. It's true we can't know know all of the future possibilities, ideas, and extensions for decorators, and we want to make sure that whatever design we come up with is extensible. As @ljharb has pointed out, this design can be extended with future modifiers that could introduce additional capabilities. In addition, while it might not be desirable to introduce multiple modifiers on the same decorator, I don't see any reason why this would be prohibited by this design, so it would be a possibility in the future as well.

I'm cross-posting here the reason I wrote in #430 (comment), for which I don't think we should disallow the whitespace after ::

I think we should permit a space between @init: and the identifier. @init: and the identifier are two separate things:

  • The identifier is a reference to the decorator function. The function is not named @init:foo but just foo.
  • The @init: is an "operator" which applies the foo function to a class (or a class element) in a specific way.

It's even more clear that @init: and foo are two separate things when we consider member expressions:

@init:foo.bar
class A {}

First you evaluate foo, then you get foo.bar and then (not right after foo) you apply the @init:.

I think the whitespace here is just a stylistic choice and in practice almost no one will write it, similarly to how almost no one writes +foo when using the + unary operator. However, if we really want to disallow the whitespace after @init:, we should also disallow it in DecoratorMemberExpression: being able to write @init:foo . bar but not @init: foo.bar makes it feel like @init: has an higher precedence than ..

I don't feel the same about whitespaces inside @init:: while allowing them would be consistent with other "it's actually a single thing but made up of multiple tokens" syntaxes (e.g. new.target can be written as new . target), it's still executed as a single piece.

@nicolo-ribaudo what about whitespace in @foo.bar between the @ and the foo?

If we allow @ foo, then I think it would be quite strange if we do not also allow @ init : foo.

Conversely, if we require @foo (which i think we should), then i think it would be strange if we did not require @init:foo.

@init: has been removed (#436), so this issue is now moot. #430 is still a question, however.