Skip to content

Bug: JSDocNonNullableType has extra nested typeAnnotation with invalid position #11064

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
4 tasks done
leaysgur opened this issue Apr 14, 2025 · 3 comments
Closed
4 tasks done
Labels
bug Something isn't working locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. wontfix This will not be worked on working as intended Issues that are closed as they are working as intended

Comments

@leaysgur
Copy link

Before You File a Bug Report Please Confirm You Have Done The Following...

  • I have tried restarting my IDE and the issue persists.
  • I have updated to the latest version of the packages.
  • I have searched for related issues and found none that matched my issue.
  • I have read the FAQ and my problem is not listed.

Relevant Package

parser

Playground Link

https://typescript-eslint.io/play/#ts=5.4.3&showAST=es&fileType=.tsx&code=GYVwdgxgLglg9mABMAjACgIYC5EbATwEocNEYBnRcqAJxjAHMBCAbwChFEaBTKEGpLRDcA3GwC%2BbIA&eslintrc=N4KABGBEBOCuA2BTAzpAXGYBfEWg&tsconfig=N4KABGBEDGD2C2AHAlgGwKYCcDyiAuysAdgM6QBcYoEEkJemy0eAcgK6qoDCAFutAGsylBm3TgwAXxCSgA&tokens=false

Repro Code

function f1(a: any): a is string!{
  return true;
}

ESLint Config

tsconfig

Expected Result

{
  "type": "Program",
  "start": 0,
  "end": 51,
  "body": [
    {
      "type": "FunctionDeclaration",
      "start": 0,
      "end": 51,
      "id": {
        "type": "Identifier",
        "start": 9,
        "end": 11,
        "name": "f1",
        "decorators": [],
        "optional": false,
        "typeAnnotation": null
      },
      "expression": false,
      "generator": false,
      "async": false,
      "params": [
        {
          "type": "Identifier",
          "start": 12,
          "end": 18,
          "name": "a",
          "decorators": [],
          "optional": false,
          "typeAnnotation": {
            "type": "TSTypeAnnotation",
            "start": 13,
            "end": 18,
            "typeAnnotation": {
              "type": "TSAnyKeyword",
              "start": 15,
              "end": 18
            }
          }
        }
      ],
      "body": {
        "type": "BlockStatement",
        "start": 33,
        "end": 51,
        "body": [
          {
            "type": "ReturnStatement",
            "start": 37,
            "end": 49,
            "argument": {
              "type": "Literal",
              "start": 44,
              "end": 48,
              "value": true,
              "raw": "true"
            }
          }
        ]
      },
      "declare": false,
      "typeParameters": null,
      "returnType": {
        "type": "TSTypeAnnotation",
        "start": 19,
        "end": 33,
        "typeAnnotation": {
          "type": "TSTypePredicate",
          "start": 21,
          "end": 33,
          "parameterName": {
            "type": "Identifier",
            "start": 21,
            "end": 22,
            "name": "a",
            "decorators": [],
            "optional": false,
            "typeAnnotation": null
          },
          "asserts": false,
          "typeAnnotation": { // ============================== 👀
            "type": "TSTypeAnnotation",
            "start": 26,
            "end": 33,
            "typeAnnotation": {
              "type": "TSJSDocNonNullableType",
              "start": 26,
              "end": 33,
              "typeAnnotation": {
                "type": "TSStringKeyword",
                "start": 26,
                "end": 32
              },
              "postfix": true
            } // ============================================= 👀
          }
        }
      }
    }
  ],
  "sourceType": "module"
}

Actual Result

{
  "type": "Program",
  "start": 0,
  "end": 51,
  "body": [
    {
      "type": "FunctionDeclaration",
      "start": 0,
      "end": 51,
      "async": false,
      "body": {
        "type": "BlockStatement",
        "start": 33,
        "end": 51,
        "body": [
          {
            "type": "ReturnStatement",
            "start": 37,
            "end": 49,
            "argument": {
              "type": "Literal",
              "start": 44,
              "end": 48,
              "raw": "true",
              "value": true
            }
          }
        ]
      },
      "declare": false,
      "expression": false,
      "generator": false,
      "id": {
        "type": "Identifier",
        "start": 9,
        "end": 11,
        "decorators": [],
        "name": "f1",
        "optional": false,
        "typeAnnotation": null
      },
      "params": [
        {
          "type": "Identifier",
          "start": 12,
          "end": 18,
          "decorators": [],
          "name": "a",
          "optional": false,
          "typeAnnotation": {
            "type": "TSTypeAnnotation",
            "start": 13,
            "end": 18,
            "typeAnnotation": {
              "type": "TSAnyKeyword",
              "start": 15,
              "end": 18
            }
          }
        }
      ],
      "returnType": {
        "type": "TSTypeAnnotation",
        "start": 19,
        "end": 33,
        "typeAnnotation": {
          "type": "TSTypePredicate",
          "start": 21,
          "end": 33,
          "asserts": false,
          "parameterName": {
            "type": "Identifier",
            "start": 21,
            "end": 22,
            "decorators": [],
            "name": "a",
            "optional": false,
            "typeAnnotation": null
          },
          "typeAnnotation": { // ============================== 👀
            "type": "TSTypeAnnotation",
            "start": 26,
            "end": 33,
            "typeAnnotation": {
              "type": "TSJSDocNonNullableType",
              "start": 26,
              "end": 33,
              "typeAnnotation": {
                "type": "TSTypeAnnotation",
                "start": 24, // ❗️
                "end": 32,
                "typeAnnotation": {
                  "type": "TSStringKeyword",
                  "start": 26,
                  "end": 32
                }
              },
              "id": 0,
              "original": null,
              "emitNode": null,
              "postfix": true
            }
          }
        } // ================================================== 👀
      },
      "typeParameters": null
    }
  ],
  "sourceType": "module"
}

Additional Info

I'm not sure if extra typeAnnotation nesting is intended or not, but at least its starting position should be fixed?

Versions

package version
@typescript-eslint/parser 8.29.1
@leaysgur leaysgur added bug Something isn't working triage Waiting for team members to take a look labels Apr 14, 2025
@kirkwaiblinger
Copy link
Member

Hi @leaysgur! I think this is just a consequence of the string! being illegal syntax.... In your playground link I get

'!' at the end of a type is not valid TypeScript syntax. Did you mean to write 'string'?(17019)

Do you have an example of syntactically valid TS with unexpected AST?

@leaysgur
Copy link
Author

Thank you for triage! ✋🏻

Do you have an example of syntactically valid TS with unexpected AST?

No, I don't have it. This code is from a TypeScript test case.

https://github.com/microsoft/TypeScript/blob/main/tests/cases/compiler/parseInvalidNonNullableTypes.ts

If noCheck: true, the TS itself does not seem to show any errors, how is such a case handled as TS-ESLint? 👀

https://www.typescriptlang.org/play/?noCheck=true#code/GYVwdgxgLglg9mABMAjACgIYC5EbATwEocNEYBnRcqAJxjAHMBCAbwChFEaBTKEGpLRDcA3GwC+QA

@bradzacher
Copy link
Member

bradzacher commented Apr 14, 2025

We're built on top of TS and TS is a forgiving parser.
It will recover from invalid syntax as best it can and produce an AST so that that AST can be used to power IDE features. This is why you can still have autocomplete and hover types even if you've got syntax errors!

Because of this behaviour TS doesn't generally emit many true, crashing syntax errors. Most of its "syntax" errors are instead emitted as what it defines as "semantic" errors. This has tripped some users up in the past -- but just because they're not crashing syntax errors doesn't mean they're not syntax errors -- TS just emits them as semantic errors so that downstream tools can recover and continue working if they desire (eg the mentioned IDE usecase).

We have some errors in place to try and catch bad syntax so we don't produce an invalid AST. We could add a bunch more errors around the place -- but because it's forgiving parser that auto-recovers -- the cases are realistically going to be limitless and it's going to be difficult to check every possible case without tanking performance.

In general our philosophy is "if you have TS errors in your code then expect undefined behaviour from our rules". This applies to any form of TS error be it type error or "semantic syntax error" (which your case is the latter).

@bradzacher bradzacher closed this as not planned Won't fix, can't repro, duplicate, stale Apr 14, 2025
@bradzacher bradzacher added working as intended Issues that are closed as they are working as intended wontfix This will not be worked on and removed triage Waiting for team members to take a look labels Apr 14, 2025
@github-actions github-actions bot added the locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. label Apr 22, 2025
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 22, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. wontfix This will not be worked on working as intended Issues that are closed as they are working as intended
Projects
None yet
Development

No branches or pull requests

3 participants