Skip to content

Commit 43b8e15

Browse files
committed
fix #2306: split << when used in type arguments
1 parent 8af93a9 commit 43b8e15

File tree

3 files changed

+42
-6
lines changed

3 files changed

+42
-6
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@
22

33
## Unreleased
44

5+
* Fix TypeScript parse error whe a generic function is the first type argument ([#2306](https://github.com/evanw/esbuild/issues/2306))
6+
7+
In TypeScript, the `<<` token may need to be split apart into two `<` tokens if it's present in a type argument context. This was already correctly handled for all type expressions and for identifier expressions such as in the following code:
8+
9+
```ts
10+
// These cases already worked in the previous release
11+
let foo: Array<<T>() => T>;
12+
bar<<T>() => T>;
13+
```
14+
15+
However, normal expressions of the following form were previously incorrectly treated as syntax errors:
16+
17+
```ts
18+
// These cases were broken but have now been fixed
19+
foo.bar<<T>() => T>;
20+
foo?.<<T>() => T>();
21+
```
22+
23+
With this release, these cases now parsed correctly.
24+
525
* Fix minification regression with pure IIFEs ([#2279](https://github.com/evanw/esbuild/issues/2279))
626

727
An Immediately Invoked Function Expression (IIFE) is a function call to an anonymous function, and is a way of introducing a new function-level scope in JavaScript since JavaScript lacks a way to do this otherwise. And a pure function call is a function call with the special `/* @__PURE__ */` comment before it, which tells JavaScript build tools that the function call can be considered to have no side effects (and can be removed if it's unused).

internal/js_parser/js_parser.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3014,11 +3014,6 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF
30143014
})}
30153015
}
30163016

3017-
// Allow "const a = b<c>"
3018-
if p.options.ts.Parse {
3019-
p.trySkipTypeScriptTypeArgumentsWithBacktracking()
3020-
}
3021-
30223017
ref := p.storeNameInRef(name)
30233018
return js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}
30243019

@@ -3679,8 +3674,9 @@ func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredE
36793674
OptionalChain: optionalStart,
36803675
}}
36813676

3682-
case js_lexer.TLessThan:
3677+
case js_lexer.TLessThan, js_lexer.TLessThanLessThan:
36833678
// "a?.<T>()"
3679+
// "a?.<<T>() => T>()"
36843680
if !p.options.ts.Parse {
36853681
p.lexer.Expected(js_lexer.TIdentifier)
36863682
}
@@ -4013,6 +4009,14 @@ func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredE
40134009
left = js_ast.Expr{Loc: left.Loc, Data: &js_ast.EBinary{Op: js_ast.BinOpGe, Left: left, Right: p.parseExpr(js_ast.LCompare)}}
40144010

40154011
case js_lexer.TLessThanLessThan:
4012+
// TypeScript allows type arguments to be specified with angle brackets
4013+
// inside an expression. Unlike in other languages, this unfortunately
4014+
// appears to require backtracking to parse.
4015+
if p.options.ts.Parse && p.trySkipTypeScriptTypeArgumentsWithBacktracking() {
4016+
optionalChain = oldOptionalChain
4017+
continue
4018+
}
4019+
40164020
if level >= js_ast.LShift {
40174021
return left
40184022
}

internal/js_parser/ts_parser_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1876,6 +1876,8 @@ func TestTSInstantiationExpression(t *testing.T) {
18761876

18771877
expectPrintedTS(t, "f<number>?.();", "f?.();\n")
18781878
expectPrintedTS(t, "f?.<number>();", "f?.();\n")
1879+
expectPrintedTS(t, "f<<T>() => T>?.();", "f?.();\n")
1880+
expectPrintedTS(t, "f?.<<T>() => T>();", "f?.();\n")
18791881

18801882
expectPrintedTS(t, "f<number>['g'];", "f < number > [\"g\"];\n")
18811883

@@ -1885,6 +1887,9 @@ func TestTSInstantiationExpression(t *testing.T) {
18851887
// This behavior matches TypeScript 4.7.0 nightly (specifically "typescript@4.7.0-dev.20220421")
18861888
// after various fixes from Microsoft that landed after the TypeScript 4.7.0 beta
18871889
expectPrintedTS(t, "f<x>, g<y>;", "f, g;\n")
1890+
expectPrintedTS(t, "f<<T>() => T>;", "f;\n")
1891+
expectPrintedTS(t, "f.x<<T>() => T>;", "f.x;\n")
1892+
expectPrintedTS(t, "f['x']<<T>() => T>;", "f[\"x\"];\n")
18881893
expectPrintedTS(t, "f<x>g<y>;", "f < x > g;\n")
18891894
expectPrintedTS(t, "f<x>=g<y>;", "f = g;\n")
18901895
expectPrintedTS(t, "f<x>>g<y>;", "f < x >> g;\n")
@@ -2193,9 +2198,13 @@ func TestTSTypeOnlyExport(t *testing.T) {
21932198

21942199
func TestTSOptionalChain(t *testing.T) {
21952200
expectParseError(t, "a?.<T>()", "<stdin>: ERROR: Expected identifier but found \"<\"\n")
2201+
expectParseError(t, "a?.<<T>() => T>()", "<stdin>: ERROR: Expected identifier but found \"<<\"\n")
21962202
expectPrintedTS(t, "a?.<T>()", "a?.();\n")
2203+
expectPrintedTS(t, "a?.<<T>() => T>()", "a?.();\n")
21972204
expectParseErrorTS(t, "a?.<T>b", "<stdin>: ERROR: Expected \"(\" but found \"b\"\n")
21982205
expectParseErrorTS(t, "a?.<T>[b]", "<stdin>: ERROR: Expected \"(\" but found \"[\"\n")
2206+
expectParseErrorTS(t, "a?.<<T>() => T>b", "<stdin>: ERROR: Expected \"(\" but found \"b\"\n")
2207+
expectParseErrorTS(t, "a?.<<T>() => T>[b]", "<stdin>: ERROR: Expected \"(\" but found \"[\"\n")
21992208

22002209
expectPrintedTS(t, "a?.b.c", "a?.b.c;\n")
22012210
expectPrintedTS(t, "(a?.b).c", "(a?.b).c;\n")
@@ -2208,7 +2217,10 @@ func TestTSOptionalChain(t *testing.T) {
22082217
expectPrintedTS(t, "a?.b(c)", "a?.b(c);\n")
22092218
expectPrintedTS(t, "(a?.b)(c)", "(a?.b)(c);\n")
22102219
expectPrintedTS(t, "a?.b!(c)", "a?.b(c);\n")
2220+
22112221
expectPrintedTS(t, "a?.b<T>(c)", "a?.b(c);\n")
2222+
expectPrintedTS(t, "a?.b<+T>(c)", "a?.b < +T > c;\n")
2223+
expectPrintedTS(t, "a?.b<<T>() => T>(c)", "a?.b(c);\n")
22122224
}
22132225

22142226
func TestTSJSX(t *testing.T) {

0 commit comments

Comments
 (0)