tc39/proposal-decorators

Request: Syntactic carve-out for `@@`-decorators for design-time space

Closed this issue · 4 comments

Early on in the life of the Decorators proposal, I had discussed with some stakeholders at the time about the possibility of introducing a syntactic carve-out for "design-time only" decorators that are handled purely by build tools (i.e., TypeScript, Babel, Flow, esbuild, etc.). While such a carve-out has been discussed, it has never been formalized.

This syntactic carve-out would be roughly the same syntax proposed for Decorator:

DesignTimeDecorator [Yield, Await] :
  `@@` Identifier `:` DecoratorMemberExpression[?Yield, ?Await]
  `@@` Identifier `:` DecoratorCallExpression[?Yield, ?Await]
  `@@` DecoratorMemberExpression[?Yield, ?Await]
  `@@` DecoratorCallExpression[?Yield, ?Await]

The motivation behind this is to allow build tools to leverage decorator-like syntax to affect emit behavior without needing to use heuristics to distinguish between decorators that have runtime behavior and those that have design-time behavior. Design-time decorators could also, in theory, be applied to other declarations that currently are not valid decoration targets, such as functions, parameters, object literals, variable declarations, etc., without impacting any future proposals that might introduce runtime decorators.

For example (none of these are concrete asks, but more of a sketch of possible outcomes):

class C {
  // If DEBUG is defined, the body of `foo` is removed by the compiler, and all calls to `C.foo()` are replaced with 
  // `undefined`.
  @@conditional("DEBUG")
  static foo() { ... }

  // when not provided as arguments, a compiler could inject the name, path, and line number of the caller
  // as function arguments.
  trace(
    @@callerMemberName memberName = "",
    @@callerFilePath filePath = "",
    @@callerLineNumber lineNumber = 0) {
    ...
  }

  // compiler can inline the field into the constructor using [Set] semantics instead of [Define] semantics.
  @@useSet x = 1;
}

// report a warning during compilation when this function is called.
@@deprecated("No longer supported")
function bar() {}

Without design-time decorators, tools are reduced to using comments (such as TypeScript's support for /** @deprecated */).

It would be up to the build tool in question to determine how to handle the syntax, but ECMA-262 would consider the above a syntax error. Alternatively, ECMA-262 could permit parsing the above syntax but treat it as something akin to a comment. The goal of the syntax carve-out would be to inform future proposals so as to avoid ambiguities between new syntax proposed for ECMA-262 and syntax used by build tools. Essentially, this is similar to the de facto carve out for : in certain contexts to allow for type annotations in ECMAScript-based languages like Flow and TypeScript, and possible future adoption of type annotations in ECMAScript.

It wouldn’t be legal syntax as-is - what would a carve out do?

Separately, i don’t think anything that lacks runtime semantics but still would end up being loaded in browsers and node is a good idea - we already have comments for that.

It wouldn’t be legal syntax as-is - what would a carve out do?

We don't currently have a formal syntax carveout for : for type annotations, only an informal one. The reason to have a formal syntax carveout is to avoid future cases where languages like TypeScript or Flow would like to extend syntax without running into future incompatibilities (specifically to avoid the cases that have come up in the past).

Separately, i don’t think anything that lacks runtime semantics but still would end up being loaded in browsers and node is a good idea - we already have comments for that.

I don't disagree. The "treat it as a comment" part of my message above was mostly along the same lines as the existing "types as comments" discussion. It's not something I would strongly push for.

If this were to go forward, what about the following syntax:

@ignore:someDecorator foo = 123
// or
@:someDecorator foo = 123

where @: is short for @ignore:, and matches with @init: syntax and with possible future additions that may come before : in @...:

I've been convinced by others that something like @design: or some other keyword could satisfy a "design-time only" decorator scenario and withdraw this request.