Skip to content

Commit 8351c41

Browse files
authored
add support of TS Optional variance annotations (#2102)
1 parent cbe80de commit 8351c41

File tree

4 files changed

+172
-14
lines changed

4 files changed

+172
-14
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
* Add support for parsing "optional variance annotations" from TypeScript 4.7 ([#2102](https://github.com/evanw/esbuild/pull/2102))
6+
7+
The upcoming version of TypeScript now lets you specify `in` and/or `out` on certain type parameters (specifically only on a type alias, an interface declaration, or a class declaration). These modifiers control type paramemter covariance and contravariance:
8+
9+
```ts
10+
type Provider<out T> = () => T;
11+
type Consumer<in T> = (x: T) => void;
12+
type Mapper<in T, out U> = (x: T) => U;
13+
type Processor<in out T> = (x: T) => T;
14+
```
15+
16+
With this release, esbuild can now parse these new type parameter modifiers. This feature was contributed by [@magic-akari](https://github.com/magic-akari).
17+
318
## 0.14.30
419

520
* Change the context of TypeScript parameter decorators ([#2147](https://github.com/evanw/esbuild/issues/2147))

internal/js_parser/js_parser.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1928,7 +1928,7 @@ func (p *parser) parseProperty(kind js_ast.PropertyKind, opts propertyOpts, erro
19281928

19291929
// "class X { foo?<T>(): T }"
19301930
// "const x = { foo<T>(): T {} }"
1931-
p.skipTypeScriptTypeParameters()
1931+
p.skipTypeScriptTypeParameters(typeParametersNormal)
19321932
}
19331933

19341934
// Parse a class field with an optional initial value
@@ -2490,7 +2490,7 @@ func (p *parser) parseFnExpr(loc logger.Loc, isAsync bool, asyncRange logger.Ran
24902490

24912491
// Even anonymous functions can have TypeScript type parameters
24922492
if p.options.ts.Parse {
2493-
p.skipTypeScriptTypeParameters()
2493+
p.skipTypeScriptTypeParameters(typeParametersNormal)
24942494
}
24952495

24962496
await := allowIdent
@@ -3139,7 +3139,7 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF
31393139

31403140
// Even anonymous classes can have TypeScript type parameters
31413141
if p.options.ts.Parse {
3142-
p.skipTypeScriptTypeParameters()
3142+
p.skipTypeScriptTypeParameters(typeParametersNormal)
31433143
}
31443144

31453145
class := p.parseClass(classKeyword, name, parseClassOpts{})
@@ -3347,7 +3347,7 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF
33473347
// <A = B>(x) => {}
33483348

33493349
if p.options.ts.Parse && p.options.jsx.Parse && p.isTSArrowFnJSX() {
3350-
p.skipTypeScriptTypeParameters()
3350+
p.skipTypeScriptTypeParameters(typeParametersNormal)
33513351
p.lexer.Expect(js_lexer.TOpenParen)
33523352
return p.parseParenExpr(loc, level, parenExprOpts{forceArrowFn: true})
33533353
}
@@ -5387,7 +5387,7 @@ func (p *parser) parseClassStmt(loc logger.Loc, opts parseStmtOpts) js_ast.Stmt
53875387

53885388
// Even anonymous classes can have TypeScript type parameters
53895389
if p.options.ts.Parse {
5390-
p.skipTypeScriptTypeParameters()
5390+
p.skipTypeScriptTypeParameters(typeParametersWithInOutVarianceAnnotations)
53915391
}
53925392

53935393
classOpts := parseClassOpts{
@@ -5647,7 +5647,7 @@ func (p *parser) parseFnStmt(loc logger.Loc, opts parseStmtOpts, isAsync bool, a
56475647

56485648
// Even anonymous functions can have TypeScript type parameters
56495649
if p.options.ts.Parse {
5650-
p.skipTypeScriptTypeParameters()
5650+
p.skipTypeScriptTypeParameters(typeParametersNormal)
56515651
}
56525652

56535653
// Introduce a fake block scope for function declarations inside if statements

internal/js_parser/ts_parser.go

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,12 @@ func (p *parser) skipTypeScriptTypeWithOpts(level js_ast.L, opts skipTypeOpts) {
277277
return
278278
}
279279

280-
p.skipTypeScriptTypeParameters()
280+
p.skipTypeScriptTypeParameters(typeParametersNormal)
281281
p.skipTypeScriptParenOrFnType()
282282

283283
case js_lexer.TLessThan:
284284
// "<T>() => Foo<T>"
285-
p.skipTypeScriptTypeParameters()
285+
p.skipTypeScriptTypeParameters(typeParametersNormal)
286286
p.skipTypeScriptParenOrFnType()
287287

288288
case js_lexer.TOpenParen:
@@ -562,7 +562,7 @@ func (p *parser) skipTypeScriptObjectType() {
562562
}
563563

564564
// Type parameters come right after the optional mark
565-
p.skipTypeScriptTypeParameters()
565+
p.skipTypeScriptTypeParameters(typeParametersNormal)
566566

567567
switch p.lexer.Token {
568568
case js_lexer.TColon:
@@ -603,14 +603,79 @@ func (p *parser) skipTypeScriptObjectType() {
603603
p.lexer.Expect(js_lexer.TCloseBrace)
604604
}
605605

606+
type typeParameters uint8
607+
608+
const (
609+
typeParametersNormal typeParameters = iota
610+
typeParametersWithInOutVarianceAnnotations
611+
)
612+
606613
// This is the type parameter declarations that go with other symbol
607614
// declarations (class, function, type, etc.)
608-
func (p *parser) skipTypeScriptTypeParameters() {
615+
func (p *parser) skipTypeScriptTypeParameters(mode typeParameters) {
609616
if p.lexer.Token == js_lexer.TLessThan {
610617
p.lexer.Next()
611618

612619
for {
613-
p.lexer.Expect(js_lexer.TIdentifier)
620+
hasIn := false
621+
hasOut := false
622+
expectIdentifier := true
623+
invalidModifierRange := logger.Range{}
624+
625+
// Scan over a sequence of "in" and "out" modifiers (a.k.a. optional variance annotations)
626+
for {
627+
if p.lexer.Token == js_lexer.TIn {
628+
if invalidModifierRange.Len == 0 && (mode != typeParametersWithInOutVarianceAnnotations || hasIn || hasOut) {
629+
// Valid:
630+
// "type Foo<in T> = T"
631+
// Invalid:
632+
// "type Foo<in in T> = T"
633+
// "type Foo<out in T> = T"
634+
invalidModifierRange = p.lexer.Range()
635+
}
636+
p.lexer.Next()
637+
hasIn = true
638+
expectIdentifier = true
639+
continue
640+
}
641+
642+
if p.lexer.IsContextualKeyword("out") {
643+
r := p.lexer.Range()
644+
if invalidModifierRange.Len == 0 && mode != typeParametersWithInOutVarianceAnnotations {
645+
invalidModifierRange = r
646+
}
647+
p.lexer.Next()
648+
if invalidModifierRange.Len == 0 && hasOut && (p.lexer.Token == js_lexer.TIn || p.lexer.Token == js_lexer.TIdentifier) {
649+
// Valid:
650+
// "type Foo<out T> = T"
651+
// "type Foo<out out> = T"
652+
// "type Foo<out out, T> = T"
653+
// "type Foo<out out = T> = T"
654+
// "type Foo<out out extends T> = T"
655+
// Invalid:
656+
// "type Foo<out out in T> = T"
657+
// "type Foo<out out T> = T"
658+
invalidModifierRange = r
659+
}
660+
hasOut = true
661+
expectIdentifier = false
662+
continue
663+
}
664+
665+
break
666+
}
667+
668+
// Only report an error for the first invalid modifier
669+
if invalidModifierRange.Len > 0 {
670+
p.log.Add(logger.Error, &p.tracker, invalidModifierRange, fmt.Sprintf(
671+
"The modifier %q is not valid here:", p.source.TextForRange(invalidModifierRange)))
672+
}
673+
674+
// expectIdentifier => Mandatory identifier (e.g. after "type Foo <in ___")
675+
// !expectIdentifier => Optional identifier (e.g. after "type Foo <out ___" since "out" may be the identifier)
676+
if expectIdentifier || p.lexer.Token == js_lexer.TIdentifier {
677+
p.lexer.Expect(js_lexer.TIdentifier)
678+
}
614679

615680
// "class Foo<T extends number> {}"
616681
if p.lexer.Token == js_lexer.TExtends {
@@ -701,7 +766,7 @@ func (p *parser) trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking()
701766
}
702767
}()
703768

704-
p.skipTypeScriptTypeParameters()
769+
p.skipTypeScriptTypeParameters(typeParametersNormal)
705770
if p.lexer.Token != js_lexer.TOpenParen {
706771
p.lexer.Unexpected()
707772
}
@@ -836,7 +901,7 @@ func (p *parser) skipTypeScriptInterfaceStmt(opts parseStmtOpts) {
836901
p.localTypeNames[name] = true
837902
}
838903

839-
p.skipTypeScriptTypeParameters()
904+
p.skipTypeScriptTypeParameters(typeParametersWithInOutVarianceAnnotations)
840905

841906
if p.lexer.Token == js_lexer.TExtends {
842907
p.lexer.Next()
@@ -883,7 +948,7 @@ func (p *parser) skipTypeScriptTypeStmt(opts parseStmtOpts) {
883948
p.localTypeNames[name] = true
884949
}
885950

886-
p.skipTypeScriptTypeParameters()
951+
p.skipTypeScriptTypeParameters(typeParametersWithInOutVarianceAnnotations)
887952
p.lexer.Expect(js_lexer.TEquals)
888953
p.skipTypeScriptType(js_ast.LLowest)
889954
p.lexer.ExpectOrInsertSemicolon()

internal/js_parser/ts_parser_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,84 @@ func TestTSTypes(t *testing.T) {
287287
expectParseErrorTS(t, "let x: abstract () => void = Foo", "<stdin>: ERROR: Expected \";\" but found \"(\"\n")
288288
expectParseErrorTS(t, "let x: abstract <T>() => Foo<T>", "<stdin>: ERROR: Expected \";\" but found \"(\"\n")
289289
expectParseErrorTS(t, "let x: abstract <T extends object>() => Foo<T>", "<stdin>: ERROR: Expected \"?\" but found \">\"\n")
290+
291+
// TypeScript 4.7
292+
jsxErrorArrow := "<stdin>: ERROR: The character \">\" is not valid inside a JSX element\n" +
293+
"NOTE: Did you mean to escape it as \"{'>'}\" instead?\n" +
294+
"<stdin>: ERROR: Unexpected end of file\n"
295+
expectPrintedTS(t, "type Foo<in T> = T", "")
296+
expectPrintedTS(t, "type Foo<out T> = T", "")
297+
expectPrintedTS(t, "type Foo<in out> = T", "")
298+
expectPrintedTS(t, "type Foo<out out> = T", "")
299+
expectPrintedTS(t, "type Foo<in out out> = T", "")
300+
expectPrintedTS(t, "type Foo<in X, out Y> = [X, Y]", "")
301+
expectPrintedTS(t, "type Foo<out X, in Y> = [X, Y]", "")
302+
expectPrintedTS(t, "type Foo<out X, out Y extends keyof X> = [X, Y]", "")
303+
expectParseErrorTS(t, "type Foo<i\\u006E T> = T", "<stdin>: ERROR: Expected identifier but found \"i\\\\u006E\"\n")
304+
expectParseErrorTS(t, "type Foo<ou\\u0074 T> = T", "<stdin>: ERROR: Expected \">\" but found \"T\"\n")
305+
expectParseErrorTS(t, "type Foo<in in> = T", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n<stdin>: ERROR: Expected identifier but found \">\"\n")
306+
expectParseErrorTS(t, "type Foo<out in> = T", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n<stdin>: ERROR: Expected identifier but found \">\"\n")
307+
expectParseErrorTS(t, "type Foo<out in T> = T", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
308+
expectParseErrorTS(t, "type Foo<public T> = T", "<stdin>: ERROR: Expected \">\" but found \"T\"\n")
309+
expectParseErrorTS(t, "type Foo<in out in T> = T", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
310+
expectParseErrorTS(t, "type Foo<in out out T> = T", "<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
311+
expectPrintedTS(t, "class Foo<in T> {}", "class Foo {\n}\n")
312+
expectPrintedTS(t, "class Foo<out T> {}", "class Foo {\n}\n")
313+
expectPrintedTS(t, "export default class Foo<in T> {}", "export default class Foo {\n}\n")
314+
expectPrintedTS(t, "export default class Foo<out T> {}", "export default class Foo {\n}\n")
315+
expectPrintedTS(t, "export default class <in T> {}", "export default class {\n}\n")
316+
expectPrintedTS(t, "export default class <out T> {}", "export default class {\n}\n")
317+
expectPrintedTS(t, "interface Foo<in T> {}", "")
318+
expectPrintedTS(t, "interface Foo<out T> {}", "")
319+
expectPrintedTS(t, "declare class Foo<in T> {}", "")
320+
expectPrintedTS(t, "declare class Foo<out T> {}", "")
321+
expectPrintedTS(t, "declare interface Foo<in T> {}", "")
322+
expectPrintedTS(t, "declare interface Foo<out T> {}", "")
323+
expectParseErrorTS(t, "function foo<in T>() {}", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
324+
expectParseErrorTS(t, "function foo<out T>() {}", "<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
325+
expectParseErrorTS(t, "export default function foo<in T>() {}", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
326+
expectParseErrorTS(t, "export default function foo<out T>() {}", "<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
327+
expectParseErrorTS(t, "export default function <in T>() {}", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
328+
expectParseErrorTS(t, "export default function <out T>() {}", "<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
329+
expectParseErrorTS(t, "let foo: Foo<in T>", "<stdin>: ERROR: Unexpected \"in\"\n")
330+
expectParseErrorTS(t, "let foo: Foo<out T>", "<stdin>: ERROR: Expected \">\" but found \"T\"\n")
331+
expectParseErrorTS(t, "declare function foo<in T>()", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
332+
expectParseErrorTS(t, "declare function foo<out T>()", "<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
333+
expectParseErrorTS(t, "declare let foo: Foo<in T>", "<stdin>: ERROR: Unexpected \"in\"\n")
334+
expectParseErrorTS(t, "declare let foo: Foo<out T>", "<stdin>: ERROR: Expected \">\" but found \"T\"\n")
335+
expectParseErrorTS(t, "Foo = class <in T> {}", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
336+
expectParseErrorTS(t, "Foo = class <out T> {}", "<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
337+
expectParseErrorTS(t, "foo = function <in T>() {}", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
338+
expectParseErrorTS(t, "foo = function <out T>() {}", "<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
339+
expectParseErrorTS(t, "class Foo { foo<in T>(): T {} }", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
340+
expectParseErrorTS(t, "class Foo { foo<out T>(): T {} }", "<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
341+
expectParseErrorTS(t, "foo = { foo<in T>(): T {} }", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
342+
expectParseErrorTS(t, "foo = { foo<out T>(): T {} }", "<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
343+
expectParseErrorTS(t, "<in T>() => {}", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
344+
expectParseErrorTS(t, "<out T>() => {}", "<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
345+
expectParseErrorTS(t, "<in T, out T>() => {}", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
346+
expectParseErrorTS(t, "let x: <in T>() => {}", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
347+
expectParseErrorTS(t, "let x: <out T>() => {}", "<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
348+
expectParseErrorTS(t, "let x: <in T, out T>() => {}", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
349+
expectParseErrorTS(t, "let x: new <in T>() => {}", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
350+
expectParseErrorTS(t, "let x: new <out T>() => {}", "<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
351+
expectParseErrorTS(t, "let x: new <in T, out T>() => {}", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
352+
expectParseErrorTS(t, "let x: { y<in T>(): any }", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n")
353+
expectParseErrorTS(t, "let x: { y<out T>(): any }", "<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
354+
expectParseErrorTS(t, "let x: { y<in T, out T>(): any }", "<stdin>: ERROR: The modifier \"in\" is not valid here:\n<stdin>: ERROR: The modifier \"out\" is not valid here:\n")
355+
expectPrintedTSX(t, "<in T></in>", "/* @__PURE__ */ React.createElement(\"in\", {\n T: true\n});\n")
356+
expectPrintedTSX(t, "<out T></out>", "/* @__PURE__ */ React.createElement(\"out\", {\n T: true\n});\n")
357+
expectPrintedTSX(t, "<in out T></in>", "/* @__PURE__ */ React.createElement(\"in\", {\n out: true,\n T: true\n});\n")
358+
expectPrintedTSX(t, "<out in T></out>", "/* @__PURE__ */ React.createElement(\"out\", {\n in: true,\n T: true\n});\n")
359+
expectPrintedTSX(t, "<in T extends={true}></in>", "/* @__PURE__ */ React.createElement(\"in\", {\n T: true,\n extends: true\n});\n")
360+
expectPrintedTSX(t, "<out T extends={true}></out>", "/* @__PURE__ */ React.createElement(\"out\", {\n T: true,\n extends: true\n});\n")
361+
expectPrintedTSX(t, "<in out T extends={true}></in>", "/* @__PURE__ */ React.createElement(\"in\", {\n out: true,\n T: true,\n extends: true\n});\n")
362+
expectParseErrorTSX(t, "<in T,>() => {}", "<stdin>: ERROR: Expected \">\" but found \",\"\n")
363+
expectParseErrorTSX(t, "<out T,>() => {}", "<stdin>: ERROR: Expected \">\" but found \",\"\n")
364+
expectParseErrorTSX(t, "<in out T,>() => {}", "<stdin>: ERROR: Expected \">\" but found \",\"\n")
365+
expectParseErrorTSX(t, "<in T extends any>() => {}", jsxErrorArrow)
366+
expectParseErrorTSX(t, "<out T extends any>() => {}", jsxErrorArrow)
367+
expectParseErrorTSX(t, "<in out T extends any>() => {}", jsxErrorArrow)
290368
}
291369

292370
func TestTSAsCast(t *testing.T) {

0 commit comments

Comments
 (0)