Repository: babel/babel (TypeScript)

Issue:
[Bug]: Typescript plugin fails on named tuple positions where the name is a reserved word in JS
### 💻

- [ ] Would you like to work on a fix?

### How are you using Babel?

babel-loader (webpack)

### Input code

```
type FuncWithDescription = [
  function: (...args: any[]) => any,
  string: string
]
```

https://babeljs.io/repl#?browsers=defaults&build=&builtIns=false&corejs=3.6&spec=false&loose=false&code_lz=C4TwDgpgBAYgrgOwMYHUCWwAWARCBnJAJzTGDQHsEoBeKAbQCgooAzRJMygLigAoA6QQENCAczw8hCEHQC6AShoA-KFJAAaJlDzBiCUTx17RDWQwZJKO1gnRZcBYqQoIJsdnZz4iJTgjk09FrMdLwAHlwIcAC2AEYQhIrUKmHqUABEhBDAcIRUWNBRcQnpsprMIeGRMfGEaSDVxYnKUGEAVBoZ0XAANmRgPSBQBVBFtXilpkA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Creact%2Cstage-2%2Ctypescript&prettier=false&targets=&version=7.15.3&externalPlugins=&assumptions=%7B%7D

### Configuration file name

_No response_

### Configuration

See REPL link

### Current and expected behavior

#### Current
It throws at line 2: `/repl.tsx: Unexpected token (2:2)`
If you rename `function` to `fn`, then
it throws at line 3: `Tuple members must be labeled with a simple identifier. (3:2)`

#### Expected
It does not throw an error on either line. Why would I expect this? Because TS is totally fine with this same code: https://www.typescriptlang.org/play?ts=4.0.2#code/C4TwDgpgBAYgrgOwMYHUCWwAWARCBnJAJzTGDQHsEoBeKAbQFgAoKVqAM0STMoC4oAFADoRAQ0IBzPP1EIQdALoBKagD4oskABpmbKHmDEEE-gaMTmC5sySUDHBOiy4CxUhQTTYXJznxESHgRFGnpdNjoBAA9eBDgAWwAjCEIlGnUorSgAIkIIYDhCKixoOKSU7IUdFgjo2ITkwiyQevLU9KgogCptHPi4ABsyMAGQKBKoMsa8SssgA

### Environment

See REPL link for env details

### Possible solution

There are two ways to fix this problem, in my opinion

#### Option 1: Updated docs, and better error messages

Indicate in the docs (just like with [other caveats](https://babeljs.io/docs/en/babel-plugin-transform-typescript#caveats)) that there are certain tokens that are not acceptable for named tuple positions.

Then, ensure that the errors thrown in my example above clearly state this problem and/or reference the newly updated docs. I think the second error (`Tuple members must be labeled with a simple identifier.`) is possibly acknowledging this discrepancy, but it is not clear to me that the error is a known caveat vs an actual TS syntax issue. (The first error thrown against the word `function`, though, is quite vague.)

#### Option 2: Update the TypeScript plugin to handle these names

I do not know how many words fail in the Babel plugin, and yet succeed in TS. I do know that the following are in the list:

- `function`
- `string`
- `number`

### Additional context

_No response_
Named Tuple label : reserved keywords
<!--
Before making a PR, please read our contributing guidelines
https://github.com/babel/babel/blob/main/CONTRIBUTING.md

Please note that the Babel Team requires two approvals before merging most PRs.

For issue references: Add a comma-separated list of a [closing word](https://help.github.com/articles/closing-issues-via-commit-messages/) followed by the ticket number fixed by the PR. (it should be underlined in the preview if done correctly)

If you are making a change that should have a docs update: submit another PR to https://github.com/babel/website
-->

| Q                        | A <!--(Can use an emoji 👍) -->
| ------------------------ | ---
| Fixed Issues?            | Fixes #13702 <!-- remove the (`) quotes and write "Fixes" before the number to link the issues -->
| Patch: Bug Fix?          | 👍 
| Major: Breaking Change?  | -
| Minor: New Feature?      | -
| Tests Added + Pass?      | 👍 
| Documentation PR Link    |  - <!-- If only readme change, add `[skip ci]` to your commits -->
| Any Dependency Changes?  | -
| License                  | MIT

<!-- Describe your changes below in as much detail as possible -->
Named tuples can have labels which are reserved keywords. At the same time keywords are not valid types (i.e. type X = [function] is invalid).

<a href="https://gitpod.io/#https://github.com/babel/babel/pull/13743"><img src="https://gitpod.io/button/open-in-gitpod.svg"/></a>




Hints:
Hey @happycollision! We really appreciate you taking the time to report an issue. The  collaborators on this project attempt to help as many people as  possible, but we're a limited number of volunteers, so it's  possible this won't be addressed swiftly.

If you need any help, or just have general Babel or JavaScript questions, we have a  vibrant [Slack community](https://babeljs.slack.com) that typically always has someone  willing to help. You can sign-up [here](https://slack.babeljs.io/) for an invite."
Thanks for the bug report!

The problem is in the `tsParseTupleElementType` parser function: https://github.com/babel/babel/blob/1355d0078ca4df9e38f382d2f42684b860e60f6d/packages/babel-parser/src/plugins/typescript/index.js#L882-L892

Keywords are not valid types (i.e. `type X = [function]` is invalid), so the `tsParseType()` call at line 888 fails. We should be able to fix it by:
1. Checking if the current token is a keyword (if `this.state.type.keyword` is `true`)
  1. If it is not a keyword, then we can keep the current logic.
  2. Otherwise, we should:
    1. Parse the label as an identifier, using `this.parseIdentifier(/* allow keywords */ true)`.
    2. Consume a `:` token, using `this.expect(tt.colon)`.
    3. Finish parsing the tuple element, as in https://github.com/babel/babel/blob/1355d0078ca4df9e38f382d2f42684b860e60f6d/packages/babel-parser/src/plugins/typescript/index.js#L910-L911

---

<!-- ALERT!!!!!!!
    Before submitting this comment, please:
      1. Write where to start searching the bug
      2. Write where to add a new test
    See https://github.com/babel/babel/issues/9563#issuecomment-466799807 for an example
-->

If anyone wants to help fixing this bug and it is the first time that you contribute to Babel, follow these steps: (you need to have `make` and `yarn` available on your machine)
1. **Write a comment here** to let other possible contributors know that you are working on this bug.
1. Fork the repo
1. Run `git clone https://github.com/<YOUR_USERNAME>/babel.git && cd babel`
1. Run `yarn && make bootstrap`
1. Wait :hourglass_flowing_sand: 
1. Run `make watch` (or `make build` whenever you change a file)
1. Add a test (only `input.ts`; `output.json` will be automatically generated) in https://github.com/babel/babel/tree/main/packages/babel-parser/test/fixtures/typescript/types
   - We need a test for the valid case (the example in the bug report) and for the invalid case `[function]`.
1. Update the code!
1. `yarn jest parser` to run the tests
    - If some test outputs don't match but the new results are correct, you can delete the bad `output.js` files and run the tests again
    - If you prefer, you can run `OVERWRITE=true yarn jest parser` and they will be automatically updated.
    - New parser tests sometimes fail: don't worry, run `yarn jest parser` again after that 
1. If it is working, run `make test` to run all the tests
1. Run `git push` and open a PR!
Hi! Can I pick this bug?
Sure, it's yours! If you need any help feel free to ask.
Hey @nicolo-ribaudo pardon my ignorance, I'm new to this codebase and I was wondering if there is any documentation for the internal methods such as `this.eat()` and `this.expect()`. Will greatly appreciate your assistance!
Nope, there are no docs available for internal parser methods, however you can ask on [Slack](https://slack.babeljs.io/) and you will surely find someone that can help you.
For example `eat(_token)` checks if the specified token matches the current state, if it does the methods skips it returning true, if it doesn't the methods returns false. It is just a sugar for `.matches(_token)` and `.next()`.

`expect(_token)` instead returns `unexpected()` error if the current state token does not match the provided one.
@fedeci Thank you so much for the explanation. Will definitely use the slack channel for these sorts of queries in the future!!
@aimanjaffer If you are asking because of this bug, please first leave some time (one more week) for @ChaitanyaGadodia since this bug is assigned to him :pray:
@nicolo-ribaudo Sure thing!!
@nicolo-ribaudo 
If this bug is not maintained since September, can I take it? I want to solve it and it is gonna be my first issue.
Thanks
I think the syntax is no correct, you should write this instead:
```ts
`type FuncWithDescription = {
  function: (...args: any[]) => any,
  string: string
}

const fnWithDescriptions: FuncWithDescription[] = [
  {
    function: (a,b,c) => a+b+c,
    string: 'Some string'
  }
]
```
@macluiggy 
I am defining a tuple with named positional values, not a function that has an extra property. It’s valid syntax, but Babel gets confused.

@ozanhonamlioglu
I am not a team member, but I am willing to bet you can grab it. 🤷‍♂️
@nicolo-ribaudo I see there is a PR in draft state, is this issue still available? 
Hello!
I tried to solve this issue (pr https://github.com/babel/babel/pull/15423 ), judging by the local test results (.\make test), everything seems to work locally, but some ci tests fail. Could somebody give me some guidence on this problem, please? 

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/48788/
This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/babel/babel/pr/13743/builds/170426) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 93be0ff35165a828a71f8c30ee18d8fe3cec6989:

|Sandbox| Source |
|--|--|
|[babel-repl-custom-plugin](https://codesandbox.io/s/babel-repl-custom-plugin-n7h9c)| Configuration |
|[babel-plugin-multi-config](https://codesandbox.io/s/babel-plugin-multi-config-q84ud)| Configuration |


Patch:
diff --git a/packages/babel-parser/src/plugins/typescript/index.ts b/packages/babel-parser/src/plugins/typescript/index.ts
--- a/packages/babel-parser/src/plugins/typescript/index.ts
+++ b/packages/babel-parser/src/plugins/typescript/index.ts
@@ -214,6 +214,8 @@ const TSErrors = ParseErrorEnum`typescript`({
     `Single type parameter ${typeParameterName} should have a trailing comma. Example usage: <${typeParameterName},>.`,
   StaticBlockCannotHaveModifier:
     "Static class blocks cannot have any modifier.",
+  TupleOptionalAfterType:
+    "A labeled tuple optional element must be declared using a question mark after the name and before the colon (`name?: type`), rather than after the type (`name: type?`).",
   TypeAnnotationAfterAssign:
     "Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`.",
   TypeImportCannotSpecifyDefaultAndNamed:
@@ -460,8 +462,6 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
         case "TypeParametersOrArguments":
           return this.match(tt.gt);
       }
-
-      throw new Error("Unreachable");
     }
 
     tsParseList<T extends N.Node>(
@@ -1099,34 +1099,78 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
       return this.finishNode(node, "TSTupleType");
     }
 
-    tsParseTupleElementType(): N.TsType | N.TsNamedTupleMember {
+    tsParseTupleElementType(): N.TsNamedTupleMember | N.TsType {
       // parses `...TsType[]`
 
       const { startLoc } = this.state;
 
       const rest = this.eat(tt.ellipsis);
-      let type: N.TsType | N.TsNamedTupleMember = this.tsParseType();
-      const optional = this.eat(tt.question);
-      const labeled = this.eat(tt.colon);
 
-      if (labeled) {
-        const labeledNode = this.startNodeAtNode<N.TsNamedTupleMember>(type);
-        labeledNode.optional = optional;
+      let labeled: boolean;
+      let label: N.Identifier;
+      let optional: boolean;
+      let type: N.TsNamedTupleMember | N.TsType;
+
+      const isWord = tokenIsKeywordOrIdentifier(this.state.type);
+      const chAfterWord = isWord ? this.lookaheadCharCode() : null;
+      if (chAfterWord === charCodes.colon) {
+        labeled = true;
+        optional = false;
+        label = this.parseIdentifier(true);
+        this.expect(tt.colon);
+        type = this.tsParseType();
+      } else if (chAfterWord === charCodes.questionMark) {
+        optional = true;
+        const startLoc = this.state.startLoc;
+        const wordName = this.state.value;
+        const typeOrLabel = this.tsParseNonArrayType();
+
+        if (this.lookaheadCharCode() === charCodes.colon) {
+          labeled = true;
+          label = this.createIdentifier(
+            this.startNodeAt<N.Identifier>(startLoc),
+            wordName,
+          );
+          this.expect(tt.question);
+          this.expect(tt.colon);
+          type = this.tsParseType();
+        } else {
+          labeled = false;
+          type = typeOrLabel;
+          this.expect(tt.question);
+        }
+      } else {
+        type = this.tsParseType();
+        optional = this.eat(tt.question);
+        // In this case (labeled === true) could be only in invalid label.
+        // E.g. [x.y:type]
+        // An error is raised while processing node.
+        labeled = this.eat(tt.colon);
+      }
 
-        if (
-          type.type === "TSTypeReference" &&
-          !type.typeParameters &&
-          type.typeName.type === "Identifier"
-        ) {
-          labeledNode.label = type.typeName;
+      if (labeled) {
+        let labeledNode: Undone<N.TsNamedTupleMember>;
+        if (label) {
+          labeledNode = this.startNodeAtNode<N.TsNamedTupleMember>(label);
+          labeledNode.optional = optional;
+          labeledNode.label = label;
+          labeledNode.elementType = type;
+
+          if (this.eat(tt.question)) {
+            labeledNode.optional = true;
+            this.raise(TSErrors.TupleOptionalAfterType, {
+              at: this.state.lastTokStartLoc,
+            });
+          }
         } else {
+          labeledNode = this.startNodeAtNode<N.TsNamedTupleMember>(type);
+          labeledNode.optional = optional;
           this.raise(TSErrors.InvalidTupleMemberLabel, { at: type });
           // @ts-expect-error This produces an invalid AST, but at least we don't drop
           // nodes representing the invalid source.
           labeledNode.label = type;
+          labeledNode.elementType = this.tsParseType();
         }
-
-        labeledNode.elementType = this.tsParseType();
         type = this.finishNode(labeledNode, "TSNamedTupleMember");
       } else if (optional) {
         const optionalTypeNode = this.startNodeAtNode<N.TsOptionalType>(type);


Test patch:
diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled-babel-7/input.ts b/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled-babel-7/input.ts
new file mode 100644
--- /dev/null
+++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled-babel-7/input.ts
@@ -0,0 +1,5 @@
+type FuncWithDescriptionBabel7 = [
+  function: (...args: any[]) => any,
+  string: string,
+  void?: number
+]
diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled-babel-7/options.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled-babel-7/options.json
new file mode 100644
--- /dev/null
+++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled-babel-7/options.json
@@ -0,0 +1,3 @@
+{
+  "BABEL_8_BREAKING": false
+}
diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled-babel-7/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled-babel-7/output.json
new file mode 100644
--- /dev/null
+++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled-babel-7/output.json
@@ -0,0 +1,101 @@
+{
+  "type": "File",
+  "start":0,"end":107,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":5,"column":1,"index":107}},
+  "program": {
+    "type": "Program",
+    "start":0,"end":107,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":5,"column":1,"index":107}},
+    "sourceType": "module",
+    "interpreter": null,
+    "body": [
+      {
+        "type": "TSTypeAliasDeclaration",
+        "start":0,"end":107,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":5,"column":1,"index":107}},
+        "id": {
+          "type": "Identifier",
+          "start":5,"end":30,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":30,"index":30},"identifierName":"FuncWithDescriptionBabel7"},
+          "name": "FuncWithDescriptionBabel7"
+        },
+        "typeAnnotation": {
+          "type": "TSTupleType",
+          "start":33,"end":107,"loc":{"start":{"line":1,"column":33,"index":33},"end":{"line":5,"column":1,"index":107}},
+          "elementTypes": [
+            {
+              "type": "TSNamedTupleMember",
+              "start":37,"end":70,"loc":{"start":{"line":2,"column":2,"index":37},"end":{"line":2,"column":35,"index":70}},
+              "optional": false,
+              "label": {
+                "type": "Identifier",
+                "start":37,"end":45,"loc":{"start":{"line":2,"column":2,"index":37},"end":{"line":2,"column":10,"index":45},"identifierName":"function"},
+                "name": "function"
+              },
+              "elementType": {
+                "type": "TSFunctionType",
+                "start":47,"end":70,"loc":{"start":{"line":2,"column":12,"index":47},"end":{"line":2,"column":35,"index":70}},
+                "parameters": [
+                  {
+                    "type": "RestElement",
+                    "start":48,"end":62,"loc":{"start":{"line":2,"column":13,"index":48},"end":{"line":2,"column":27,"index":62}},
+                    "argument": {
+                      "type": "Identifier",
+                      "start":51,"end":55,"loc":{"start":{"line":2,"column":16,"index":51},"end":{"line":2,"column":20,"index":55},"identifierName":"args"},
+                      "name": "args"
+                    },
+                    "typeAnnotation": {
+                      "type": "TSTypeAnnotation",
+                      "start":55,"end":62,"loc":{"start":{"line":2,"column":20,"index":55},"end":{"line":2,"column":27,"index":62}},
+                      "typeAnnotation": {
+                        "type": "TSArrayType",
+                        "start":57,"end":62,"loc":{"start":{"line":2,"column":22,"index":57},"end":{"line":2,"column":27,"index":62}},
+                        "elementType": {
+                          "type": "TSAnyKeyword",
+                          "start":57,"end":60,"loc":{"start":{"line":2,"column":22,"index":57},"end":{"line":2,"column":25,"index":60}}
+                        }
+                      }
+                    }
+                  }
+                ],
+                "typeAnnotation": {
+                  "type": "TSTypeAnnotation",
+                  "start":64,"end":70,"loc":{"start":{"line":2,"column":29,"index":64},"end":{"line":2,"column":35,"index":70}},
+                  "typeAnnotation": {
+                    "type": "TSAnyKeyword",
+                    "start":67,"end":70,"loc":{"start":{"line":2,"column":32,"index":67},"end":{"line":2,"column":35,"index":70}}
+                  }
+                }
+              }
+            },
+            {
+              "type": "TSNamedTupleMember",
+              "start":74,"end":88,"loc":{"start":{"line":3,"column":2,"index":74},"end":{"line":3,"column":16,"index":88}},
+              "optional": false,
+              "label": {
+                "type": "Identifier",
+                "start":74,"end":80,"loc":{"start":{"line":3,"column":2,"index":74},"end":{"line":3,"column":8,"index":80},"identifierName":"string"},
+                "name": "string"
+              },
+              "elementType": {
+                "type": "TSStringKeyword",
+                "start":82,"end":88,"loc":{"start":{"line":3,"column":10,"index":82},"end":{"line":3,"column":16,"index":88}}
+              }
+            },
+            {
+              "type": "TSNamedTupleMember",
+              "start":92,"end":105,"loc":{"start":{"line":4,"column":2,"index":92},"end":{"line":4,"column":15,"index":105}},
+              "optional": true,
+              "label": {
+                "type": "Identifier",
+                "start":92,"end":96,"loc":{"start":{"line":4,"column":2,"index":92},"end":{"line":4,"column":6,"index":96},"identifierName":"void"},
+                "name": "void"
+              },
+              "elementType": {
+                "type": "TSNumberKeyword",
+                "start":99,"end":105,"loc":{"start":{"line":4,"column":9,"index":99},"end":{"line":4,"column":15,"index":105}}
+              }
+            }
+          ]
+        }
+      }
+    ],
+    "directives": []
+  }
+}
diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled/input.ts b/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled/input.ts
new file mode 100644
--- /dev/null
+++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled/input.ts
@@ -0,0 +1,5 @@
+type FuncWithDescription = [
+  function: (...args: any[]) => any,
+  string: string,
+  void?: number
+]
diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled/options.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled/options.json
new file mode 100644
--- /dev/null
+++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled/options.json
@@ -0,0 +1,3 @@
+{
+  "BABEL_8_BREAKING": true
+}
diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled/output.json
new file mode 100644
--- /dev/null
+++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-keyword-labeled/output.json
@@ -0,0 +1,101 @@
+{
+  "type": "File",
+  "start":0,"end":101,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":5,"column":1,"index":101}},
+  "program": {
+    "type": "Program",
+    "start":0,"end":101,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":5,"column":1,"index":101}},
+    "sourceType": "module",
+    "interpreter": null,
+    "body": [
+      {
+        "type": "TSTypeAliasDeclaration",
+        "start":0,"end":101,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":5,"column":1,"index":101}},
+        "id": {
+          "type": "Identifier",
+          "start":5,"end":24,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":24,"index":24},"identifierName":"FuncWithDescription"},
+          "name": "FuncWithDescription"
+        },
+        "typeAnnotation": {
+          "type": "TSTupleType",
+          "start":27,"end":101,"loc":{"start":{"line":1,"column":27,"index":27},"end":{"line":5,"column":1,"index":101}},
+          "elementTypes": [
+            {
+              "type": "TSNamedTupleMember",
+              "start":31,"end":64,"loc":{"start":{"line":2,"column":2,"index":31},"end":{"line":2,"column":35,"index":64}},
+              "optional": false,
+              "label": {
+                "type": "Identifier",
+                "start":31,"end":39,"loc":{"start":{"line":2,"column":2,"index":31},"end":{"line":2,"column":10,"index":39},"identifierName":"function"},
+                "name": "function"
+              },
+              "elementType": {
+                "type": "TSFunctionType",
+                "start":41,"end":64,"loc":{"start":{"line":2,"column":12,"index":41},"end":{"line":2,"column":35,"index":64}},
+                "params": [
+                  {
+                    "type": "RestElement",
+                    "start":42,"end":56,"loc":{"start":{"line":2,"column":13,"index":42},"end":{"line":2,"column":27,"index":56}},
+                    "argument": {
+                      "type": "Identifier",
+                      "start":45,"end":49,"loc":{"start":{"line":2,"column":16,"index":45},"end":{"line":2,"column":20,"index":49},"identifierName":"args"},
+                      "name": "args"
+                    },
+                    "typeAnnotation": {
+                      "type": "TSTypeAnnotation",
+                      "start":49,"end":56,"loc":{"start":{"line":2,"column":20,"index":49},"end":{"line":2,"column":27,"index":56}},
+                      "typeAnnotation": {
+                        "type": "TSArrayType",
+                        "start":51,"end":56,"loc":{"start":{"line":2,"column":22,"index":51},"end":{"line":2,"column":27,"index":56}},
+                        "elementType": {
+                          "type": "TSAnyKeyword",
+                          "start":51,"end":54,"loc":{"start":{"line":2,"column":22,"index":51},"end":{"line":2,"column":25,"index":54}}
+                        }
+                      }
+                    }
+                  }
+                ],
+                "returnType": {
+                  "type": "TSTypeAnnotation",
+                  "start":58,"end":64,"loc":{"start":{"line":2,"column":29,"index":58},"end":{"line":2,"column":35,"index":64}},
+                  "typeAnnotation": {
+                    "type": "TSAnyKeyword",
+                    "start":61,"end":64,"loc":{"start":{"line":2,"column":32,"index":61},"end":{"line":2,"column":35,"index":64}}
+                  }
+                }
+              }
+            },
+            {
+              "type": "TSNamedTupleMember",
+              "start":68,"end":82,"loc":{"start":{"line":3,"column":2,"index":68},"end":{"line":3,"column":16,"index":82}},
+              "optional": false,
+              "label": {
+                "type": "Identifier",
+                "start":68,"end":74,"loc":{"start":{"line":3,"column":2,"index":68},"end":{"line":3,"column":8,"index":74},"identifierName":"string"},
+                "name": "string"
+              },
+              "elementType": {
+                "type": "TSStringKeyword",
+                "start":76,"end":82,"loc":{"start":{"line":3,"column":10,"index":76},"end":{"line":3,"column":16,"index":82}}
+              }
+            },
+            {
+              "type": "TSNamedTupleMember",
+              "start":86,"end":99,"loc":{"start":{"line":4,"column":2,"index":86},"end":{"line":4,"column":15,"index":99}},
+              "optional": true,
+              "label": {
+                "type": "Identifier",
+                "start":86,"end":90,"loc":{"start":{"line":4,"column":2,"index":86},"end":{"line":4,"column":6,"index":90},"identifierName":"void"},
+                "name": "void"
+              },
+              "elementType": {
+                "type": "TSNumberKeyword",
+                "start":93,"end":99,"loc":{"start":{"line":4,"column":9,"index":93},"end":{"line":4,"column":15,"index":99}}
+              }
+            }
+          ]
+        }
+      }
+    ],
+    "directives": []
+  }
+}
diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/options.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/options.json
--- a/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/options.json
+++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/options.json
@@ -2,6 +2,5 @@
   "sourceType": "module",
   "plugins": [
     "typescript"
-  ],
-  "throws": "Unexpected token, expected \",\" (1:14)"
-}
\ No newline at end of file
+  ]
+}
diff --git a/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/output.json b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/output.json
new file mode 100644
--- /dev/null
+++ b/packages/babel-parser/test/fixtures/typescript/types/tuple-labeled-invalid-optional/output.json
@@ -0,0 +1,50 @@
+{
+  "type": "File",
+  "start":0,"end":17,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":17,"index":17}},
+  "errors": [
+    "SyntaxError: A labeled tuple optional element must be declared using a question mark after the name and before the colon (`name?: type`), rather than after the type (`name: type?`). (1:14)"
+  ],
+  "program": {
+    "type": "Program",
+    "start":0,"end":17,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":17,"index":17}},
+    "sourceType": "module",
+    "interpreter": null,
+    "body": [
+      {
+        "type": "TSTypeAliasDeclaration",
+        "start":0,"end":17,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":17,"index":17}},
+        "id": {
+          "type": "Identifier",
+          "start":5,"end":6,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":6,"index":6},"identifierName":"T"},
+          "name": "T"
+        },
+        "typeAnnotation": {
+          "type": "TSTupleType",
+          "start":9,"end":16,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":16,"index":16}},
+          "elementTypes": [
+            {
+              "type": "TSNamedTupleMember",
+              "start":10,"end":15,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":15,"index":15}},
+              "optional": true,
+              "label": {
+                "type": "Identifier",
+                "start":10,"end":11,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":11,"index":11},"identifierName":"x"},
+                "name": "x"
+              },
+              "elementType": {
+                "type": "TSTypeReference",
+                "start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14}},
+                "typeName": {
+                  "type": "Identifier",
+                  "start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14},"identifierName":"A"},
+                  "name": "A"
+                }
+              }
+            }
+          ]
+        }
+      }
+    ],
+    "directives": []
+  }
+}