🔎 Search Terms
directive prologue, type-only declaration, type alias, type erasure, string expression, use client, use strict, emit, transformer, erasableSyntaxOnly
🕗 Version & Regression Information
- Reproduced with TypeScript 5.9.3 and 6.0.3.
- Reproduced on TypeScript main at 5739027c9a7 (
7.1.0-dev).
- This is the behavior in every version I tried. I could not find an existing issue for this specific interaction after searching for the terms above.
⏯ Playground Link
https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABMOcAUBKRBvAUIgxKATwAcBTRAFUQF5EBnKAJxjAHMBufQgIhAaUIAGxjkwUXtwC+QA
💻 Code
function foo() {
type T = string;
"use client";
}
🙁 Actual behavior
TypeScript emits a bare string expression after removing the type alias:
function foo() {
"use client";
}
The source string is preceded by a type-only declaration, but the emitted string is the first statement in the function body. It is therefore reinterpreted as part of the JavaScript Directive Prologue.
This changes the directive status of the string during type erasure. The same issue can affect use strict, which changes JavaScript runtime semantics, and implementation-defined directives such as use client or use server.
🙂 Expected behavior
A type-only declaration should terminate a Directive Prologue. Removing type-only syntax must not promote a following string expression into a directive. The emitted JavaScript should preserve the string as an ordinary expression statement, for example:
function foo() {
("use client");
}
Additional information about the issue
The parentheses do not change the value of the expression, but they prevent the statement from being parsed as a directive.
The purpose of this issue is to clarify whether a type-only declaration terminates a Directive Prologue or is transparent to it.
If TypeScript instead intends type-only declarations to be transparent to Directive Prologues, then source analysis should consistently recognize strings after those declarations as directives, and that TypeScript-specific extension should be documented. In either case, source interpretation and emitted JavaScript should agree.
ECMAScript context
ECMAScript defines a Directive Prologue as the longest initial sequence of string-literal expression statements in a function, script, or module body.
That rule establishes that JavaScript directives must be at the beginning and contiguous. A type alias is TypeScript-specific syntax, so the remaining question is how it participates in this sequence: whether it terminates the prologue or is transparent to it.
Ecosystem behavior
I compared parsers that support TypeScript or the equivalent Flow type-alias syntax. Their ASTs consistently classify the string after the type alias as a normal expression statement rather than a directive.
| Tool |
Source AST / directive classification |
Output after removing types |
| TypeScript |
Its strict-mode prologue scan stops at the first non-directive statement, so the type alias prevents the later string from being found |
Bare 'use client'; |
| Babel |
FunctionBody.directives is empty; the string remains an ExpressionStatement in body |
Bare 'use client'; |
@typescript-eslint/parser 8.68.0 |
ExpressionStatement.directive === undefined |
N/A |
| Yuku 0.9.1 |
ExpressionStatement.directive === null |
N/A |
| OXC 0.145.0 |
ExpressionStatement.directive === null |
Parenthesized ('use client'); |
This suggests a consistent source-level interpretation: the type-only declaration terminates the Directive Prologue.
Babel demonstrates that correct AST classification alone is not sufficient. Its TypeScript transform removes the type alias, and its generator then emits the now-leading string without parentheses, producing the same semantic change as TypeScript.
OXC preserves the AST distinction during output. Its code generator explicitly parenthesizes a leading string expression that is present in the statement list but absent from the directive list. For this input it emits:
function foo() {
("use client");
}
Either interpretation could be specified for TypeScript syntax, but the parser/binder interpretation and emitted JavaScript should not disagree.
Given the consistent ecosystem AST behavior and TypeScript's own contiguous prologue scan, I think type-only declarations should terminate the Directive Prologue, and type erasure should preserve that boundary by parenthesizing a following string expression when necessary.
🔎 Search Terms
directive prologue, type-only declaration, type alias, type erasure, string expression,
use client,use strict, emit, transformer,erasableSyntaxOnly🕗 Version & Regression Information
7.1.0-dev).⏯ Playground Link
https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABMOcAUBKRBvAUIgxKATwAcBTRAFUQF5EBnKAJxjAHMBufQgIhAaUIAGxjkwUXtwC+QA
💻 Code
🙁 Actual behavior
TypeScript emits a bare string expression after removing the type alias:
The source string is preceded by a type-only declaration, but the emitted string is the first statement in the function body. It is therefore reinterpreted as part of the JavaScript Directive Prologue.
This changes the directive status of the string during type erasure. The same issue can affect
use strict, which changes JavaScript runtime semantics, and implementation-defined directives such asuse clientoruse server.🙂 Expected behavior
A type-only declaration should terminate a Directive Prologue. Removing type-only syntax must not promote a following string expression into a directive. The emitted JavaScript should preserve the string as an ordinary expression statement, for example:
Additional information about the issue
The parentheses do not change the value of the expression, but they prevent the statement from being parsed as a directive.
The purpose of this issue is to clarify whether a type-only declaration terminates a Directive Prologue or is transparent to it.
If TypeScript instead intends type-only declarations to be transparent to Directive Prologues, then source analysis should consistently recognize strings after those declarations as directives, and that TypeScript-specific extension should be documented. In either case, source interpretation and emitted JavaScript should agree.
ECMAScript context
ECMAScript defines a Directive Prologue as the longest initial sequence of string-literal expression statements in a function, script, or module body.
That rule establishes that JavaScript directives must be at the beginning and contiguous. A type alias is TypeScript-specific syntax, so the remaining question is how it participates in this sequence: whether it terminates the prologue or is transparent to it.
Ecosystem behavior
I compared parsers that support TypeScript or the equivalent Flow type-alias syntax. Their ASTs consistently classify the string after the type alias as a normal expression statement rather than a directive.
'use client';FunctionBody.directivesis empty; the string remains anExpressionStatementinbody'use client';@typescript-eslint/parser8.68.0ExpressionStatement.directive === undefinedExpressionStatement.directive === nullExpressionStatement.directive === null('use client');This suggests a consistent source-level interpretation: the type-only declaration terminates the Directive Prologue.
Babel demonstrates that correct AST classification alone is not sufficient. Its TypeScript transform removes the type alias, and its generator then emits the now-leading string without parentheses, producing the same semantic change as TypeScript.
OXC preserves the AST distinction during output. Its code generator explicitly parenthesizes a leading string expression that is present in the statement list but absent from the directive list. For this input it emits:
Either interpretation could be specified for TypeScript syntax, but the parser/binder interpretation and emitted JavaScript should not disagree.
Given the consistent ecosystem AST behavior and TypeScript's own contiguous prologue scan, I think type-only declarations should terminate the Directive Prologue, and type erasure should preserve that boundary by parenthesizing a following string expression when necessary.