Skip to content

Commit b2c8251

Browse files
committed
fix #4192: typescript tuple label parser edge case
1 parent 28cf2f3 commit b2c8251

File tree

3 files changed

+65
-6
lines changed

3 files changed

+65
-6
lines changed

CHANGELOG.md

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

33
## Unreleased
44

5+
* Add support for certain keywords as TypeScript tuple labels ([#4192](https://github.com/evanw/esbuild/issues/4192))
6+
7+
Previously esbuild could incorrectly fail to parse certain keywords as TypeScript tuple labels that are parsed by the official TypeScript compiler if they were followed by a `?` modifier. These labels included `function`, `import`, `infer`, `new`, `readonly`, and `typeof`. With this release, these keywords will now be parsed correctly. Here's an example of some affected code:
8+
9+
```ts
10+
type Foo = [
11+
value: any,
12+
readonly?: boolean, // This is now parsed correctly
13+
]
14+
```
15+
516
* Add CSS prefixes for the `stretch` sizing value ([#4184](https://github.com/evanw/esbuild/issues/4184))
617
718
This release adds support for prefixing CSS declarations such as `div { width: stretch }`. That CSS is now transformed into this depending on what the `--target=` setting includes:

internal/js_parser/ts_parser.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ loop:
262262
p.lexer.Next()
263263

264264
// "[import: number]"
265-
if flags.has(allowTupleLabelsFlag) && p.lexer.Token == js_lexer.TColon {
265+
// "[import?: number]"
266+
if flags.has(allowTupleLabelsFlag) && (p.lexer.Token == js_lexer.TColon || p.lexer.Token == js_lexer.TQuestion) {
266267
return
267268
}
268269

@@ -288,7 +289,8 @@ loop:
288289
p.lexer.Next()
289290

290291
// "[new: number]"
291-
if flags.has(allowTupleLabelsFlag) && p.lexer.Token == js_lexer.TColon {
292+
// "[new?: number]"
293+
if flags.has(allowTupleLabelsFlag) && (p.lexer.Token == js_lexer.TColon || p.lexer.Token == js_lexer.TQuestion) {
292294
return
293295
}
294296

@@ -314,13 +316,15 @@ loop:
314316

315317
// Valid:
316318
// "[keyof: string]"
319+
// "[keyof?: string]"
317320
// "{[keyof: string]: number}"
318321
// "{[keyof in string]: number}"
319322
//
320323
// Invalid:
321324
// "A extends B ? keyof : string"
322325
//
323-
if (p.lexer.Token != js_lexer.TColon && p.lexer.Token != js_lexer.TIn) || (!flags.has(isIndexSignatureFlag) && !flags.has(allowTupleLabelsFlag)) {
326+
if (p.lexer.Token != js_lexer.TColon && p.lexer.Token != js_lexer.TQuestion && p.lexer.Token != js_lexer.TIn) ||
327+
(!flags.has(isIndexSignatureFlag) && !flags.has(allowTupleLabelsFlag)) {
324328
p.skipTypeScriptType(js_ast.LPrefix)
325329
}
326330
break loop
@@ -332,7 +336,10 @@ loop:
332336
// "type Foo = Bar extends [infer T extends string] ? T : null"
333337
// "type Foo = Bar extends [infer T extends string ? infer T : never] ? T : null"
334338
// "type Foo = { [infer in Bar]: number }"
335-
if (p.lexer.Token != js_lexer.TColon && p.lexer.Token != js_lexer.TIn) || (!flags.has(isIndexSignatureFlag) && !flags.has(allowTupleLabelsFlag)) {
339+
// "type Foo = [infer: number]"
340+
// "type Foo = [infer?: number]"
341+
if (p.lexer.Token != js_lexer.TColon && p.lexer.Token != js_lexer.TQuestion && p.lexer.Token != js_lexer.TIn) ||
342+
(!flags.has(isIndexSignatureFlag) && !flags.has(allowTupleLabelsFlag)) {
336343
p.lexer.Expect(js_lexer.TIdentifier)
337344
if p.lexer.Token == js_lexer.TExtends {
338345
p.trySkipTypeScriptConstraintOfInferTypeWithBacktracking(flags)
@@ -390,7 +397,8 @@ loop:
390397
p.lexer.Next()
391398

392399
// "[typeof: number]"
393-
if flags.has(allowTupleLabelsFlag) && p.lexer.Token == js_lexer.TColon {
400+
// "[typeof?: number]"
401+
if flags.has(allowTupleLabelsFlag) && (p.lexer.Token == js_lexer.TColon || p.lexer.Token == js_lexer.TQuestion) {
394402
return
395403
}
396404

@@ -459,12 +467,13 @@ loop:
459467

460468
default:
461469
// "[function: number]"
470+
// "[function?: number]"
462471
if flags.has(allowTupleLabelsFlag) && p.lexer.IsIdentifierOrKeyword() {
463472
if p.lexer.Token != js_lexer.TFunction {
464473
p.log.AddError(&p.tracker, p.lexer.Range(), fmt.Sprintf("Unexpected %q", p.lexer.Raw()))
465474
}
466475
p.lexer.Next()
467-
if p.lexer.Token != js_lexer.TColon {
476+
if p.lexer.Token != js_lexer.TColon && p.lexer.Token != js_lexer.TQuestion {
468477
p.lexer.Expect(js_lexer.TColon)
469478
}
470479
return

internal/js_parser/ts_parser_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,16 +369,55 @@ func TestTSTypes(t *testing.T) {
369369
expectParseErrorTS(t, "x as Foo < 1", "<stdin>: ERROR: Expected \">\" but found end of file\n")
370370

371371
// These keywords are valid tuple labels
372+
expectPrintedTS(t, "type _any = [any: string]", "")
373+
expectPrintedTS(t, "type _asserts = [asserts: string]", "")
374+
expectPrintedTS(t, "type _bigint = [bigint: string]", "")
375+
expectPrintedTS(t, "type _boolean = [boolean: string]", "")
372376
expectPrintedTS(t, "type _false = [false: string]", "")
373377
expectPrintedTS(t, "type _function = [function: string]", "")
374378
expectPrintedTS(t, "type _import = [import: string]", "")
379+
expectPrintedTS(t, "type _infer = [infer: string]", "")
380+
expectPrintedTS(t, "type _never = [never: string]", "")
375381
expectPrintedTS(t, "type _new = [new: string]", "")
376382
expectPrintedTS(t, "type _null = [null: string]", "")
383+
expectPrintedTS(t, "type _number = [number: string]", "")
384+
expectPrintedTS(t, "type _object = [object: string]", "")
385+
expectPrintedTS(t, "type _readonly = [readonly: string]", "")
386+
expectPrintedTS(t, "type _string = [string: string]", "")
387+
expectPrintedTS(t, "type _symbol = [symbol: string]", "")
377388
expectPrintedTS(t, "type _this = [this: string]", "")
378389
expectPrintedTS(t, "type _true = [true: string]", "")
379390
expectPrintedTS(t, "type _typeof = [typeof: string]", "")
391+
expectPrintedTS(t, "type _undefined = [undefined: string]", "")
392+
expectPrintedTS(t, "type _unique = [unique: string]", "")
393+
expectPrintedTS(t, "type _unknown = [unknown: string]", "")
380394
expectPrintedTS(t, "type _void = [void: string]", "")
381395

396+
// Also check tuple labels with a question mark
397+
expectPrintedTS(t, "type _any = [any?: string]", "")
398+
expectPrintedTS(t, "type _asserts = [asserts?: string]", "")
399+
expectPrintedTS(t, "type _bigint = [bigint?: string]", "")
400+
expectPrintedTS(t, "type _boolean = [boolean?: string]", "")
401+
expectPrintedTS(t, "type _false = [false?: string]", "")
402+
expectPrintedTS(t, "type _function = [function?: string]", "")
403+
expectPrintedTS(t, "type _import = [import?: string]", "")
404+
expectPrintedTS(t, "type _infer = [infer?: string]", "")
405+
expectPrintedTS(t, "type _never = [never?: string]", "")
406+
expectPrintedTS(t, "type _new = [new?: string]", "")
407+
expectPrintedTS(t, "type _null = [null?: string]", "")
408+
expectPrintedTS(t, "type _number = [number?: string]", "")
409+
expectPrintedTS(t, "type _object = [object?: string]", "")
410+
expectPrintedTS(t, "type _readonly = [readonly?: string]", "")
411+
expectPrintedTS(t, "type _string = [string?: string]", "")
412+
expectPrintedTS(t, "type _symbol = [symbol?: string]", "")
413+
expectPrintedTS(t, "type _this = [this?: string]", "")
414+
expectPrintedTS(t, "type _true = [true?: string]", "")
415+
expectPrintedTS(t, "type _typeof = [typeof?: string]", "")
416+
expectPrintedTS(t, "type _undefined = [undefined?: string]", "")
417+
expectPrintedTS(t, "type _unique = [unique?: string]", "")
418+
expectPrintedTS(t, "type _unknown = [unknown?: string]", "")
419+
expectPrintedTS(t, "type _void = [void?: string]", "")
420+
382421
// These keywords are invalid tuple labels
383422
expectParseErrorTS(t, "type _break = [break: string]", "<stdin>: ERROR: Unexpected \"break\"\n")
384423
expectParseErrorTS(t, "type _case = [case: string]", "<stdin>: ERROR: Unexpected \"case\"\n")

0 commit comments

Comments
 (0)