tree-sitter / tree-sitter-typescript

TypeScript grammar for tree-sitter
MIT License
335 stars 104 forks source link

`+` in mapped types is currently unsupported #230

Closed kevinbarabash closed 5 months ago

kevinbarabash commented 1 year ago

The following piece of code is valid but it is parsed incorrectly:

type Foo<T, K extends keyof T> = {
    +readonly [P in K]+?: T[P]
};

Here's a link to the TypeScript Playground showing that the snippet above is valid JavaScript or TypeScript: https://www.typescriptlang.org/play?#code/C4TwDgpgBAYg9nAPAFQDRQNJQgD2BAOwBMBnKAawhDgDMpkA+KAXigG8AoKbqAagCcIAQyJwCAGxBQA2gAUoASwKYAurwD8ALnpyVHAL4BuIA

Here's the output from https://tree-sitter.github.io/tree-sitter/playground:

program [0, 0] - [3, 0]
  type_alias_declaration [0, 0] - [2, 2]
    name: type_identifier [0, 5] - [0, 8]
    type_parameters: type_parameters [0, 8] - [0, 30]
      type_parameter [0, 9] - [0, 10]
        name: type_identifier [0, 9] - [0, 10]
      type_parameter [0, 12] - [0, 29]
        name: type_identifier [0, 12] - [0, 13]
        constraint: constraint [0, 14] - [0, 29]
          index_type_query [0, 22] - [0, 29]
            type_identifier [0, 28] - [0, 29]
    value: object_type [0, 33] - [2, 1]
      ERROR [1, 4] - [1, 5]
      index_signature [1, 5] - [1, 30]
        mapped_type_clause [1, 15] - [1, 21]
          name: type_identifier [1, 15] - [1, 16]
          type: type_identifier [1, 20] - [1, 21]
        ERROR [1, 22] - [1, 24]
        type: type_annotation [1, 24] - [1, 30]
          lookup_type [1, 26] - [1, 30]
            type_identifier [1, 26] - [1, 27]
            type_identifier [1, 28] - [1, 29]

Using - or + for readonly and optional modifiers in mapped types is described in the official TypeScript docs here: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers

I think the following could work as a fix (I haven't had time to verify):

index_signature: ($) =>
  seq(
    optional(seq(field("readonly_sign", optional(choice("+", "-"))), "readonly")),
    "[",
    choice(
      seq(
        field(
          "name",
          choice($.identifier, alias($._reserved_identifier, $.identifier))
        ),
        ":",
        field("index_type", $._type)
      ),
      $.mapped_type_clause
    ),
    "]",
    optional(seq(field("optional_sign", optional(choice("+", "-"))), "?")),
    field("type", $.type_annotation)
  ),