microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.7k stars 12.45k forks source link

ScriptKind.Deferred treated as LanguageVariant.Standard #60125

Open blake-newman opened 2 weeks ago

blake-newman commented 2 weeks ago

πŸ”Ž Search Terms

Deferred ScriptKind TSX Deferred ScriptKind Standard

Vue, eslint, tsx parser error

πŸ•— Version & Regression Information

Currently looking at TypeScript 5.5+ likely affects most versions of typescript.

⏯ Playground Link

No response

πŸ’» Code

Not specific to TS code but compiler and parsing.

πŸ™ Actual behavior

When using typescript-eslint with there new projectSettings option, along side vue with tsx. The file fails to parse

Component.vue
41:8  error  Parsing error: '>' expected

Deferred ScriptKinds are treated as LanguageVariant.Standard rather than LanguageVariant.JSX

https://github.com/microsoft/TypeScript/blob/main/src/compiler/parser.ts#L1746 https://github.com/microsoft/TypeScript/blob/main/src/compiler/utilities.ts#L8832

πŸ™‚ Expected behavior

Under the assumption that ScriptKind.Deferred could be treated as LanguageVariant.JSX then this resolves typescript-eslints issue with parsing extraFileExtensions such as vue files.

I'm unsure what the consequences and other use cases of ScriptKind.Deferred are so it could be as simple as adjusting getLanguageVariant.

Additional information about the issue

https://github.com/typescript-eslint/typescript-eslint/issues/9934

RyanCavanaugh commented 2 weeks ago

This would just change how the problem could happen. In a non-JSX file you might need have a <type>expr type assertion that would then parse incorrectly.

I don't know why typescript-eslint is using Deferred but that's probably the root cause here

typescript-bot commented 1 week ago

This issue has been marked as "Not a Defect" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

blake-newman commented 1 week ago

@RyanCavanaugh sorry for the delay; was looking more into the source of typescript-eslint source code to understand why it was marked as Deferred.

From what I understand extraFileExtensions such as vue are marked as Deferred as they are never part of the root files of a project. So this allows the project service to resolve the extra file extension when it is discovering files in the project. This happens as part of the project service internals and not typescript-eslint.

Marking the file as ScriptKind.TSX/TS for the extra file extensions for the project service will result in the following error:

Parsing error: ....vue was not found by the project service

This is due to the project service being unable to resolve the file as part of a project because it's not a known extension and thus means ts doesn't respect it as part of the program root files.

The reason the extra file is not part of the root files when marking as TSX is because the following code to get supported file extensions ignores any extra file extension that is not marked as Deferred.

https://github.com/microsoft/TypeScript/blob/main/src/compiler/utilities.ts#L9867 Changing to:

...mapDefined(extraFileExtensions, x => !flatBuiltins.includes(x.extension as Extension) ? [x.extension] : undefined)

Resolves the issues downstream when setting the extra file extensions. I can't see from usages why we would need to exclude any extra extensions that are not ScriptKind.Deferred.

There is also a lot of jiggery pokery in other tools such as (vue-tsc/volar](https://github.com/volarjs/volar.js/blob/master/packages/typescript/lib/quickstart/runTsc.ts#L72) to modify the code for setting extra file extensions, likely for similar reasons where by the getSupportExtensions is unable to have extra extensions that are not Deferred.

RyanCavanaugh commented 1 week ago

Changing to...

We could try a PR for this.

sheetalkamat commented 1 week ago

There is host configuration command on tsserver that lets you set script kind for file extensions .. using deferred is just default and let plugins handle through wrapping getScriptKind kind of facility so I don’t think the change you are suggesting is the one you need

sheetalkamat commented 1 week ago

From what I understand extraFileExtensions such as vue are marked as Deferred as they are never part of the root files of a project. So this allows the project service to resolve the extra file extension when it is discovering files in the project. This happens as part of the project service internals and not typescript-eslint.

Also from plugin perspective they are part of root projects and they are provided through getExternalFiles in normal editing scenario. A

blake-newman commented 1 week ago

There is host configuration command on tsserver that lets you set script kind for file extensions .. using deferred is just default and let plugins handle through wrapping getScriptKind kind of facility so I don’t think the change you are suggesting is the one you need

service.setHostConfiguration({
      extraFileExtensions: extraFileExtensions.map(extension => ({
        extension,
        isMixedContent: false,
        scriptKind: ts.ScriptKind.Deferred,
      })),
    });

Typescript eslint does what you are describing i believe.

However if you say set set scriptKind to ts.ScriptKind.TSX, the internals of getSupportedExtensions will end up stripping that extension out as it's not regarded as a Deferred script kind.

That method is then used in many places (when building up the project state). Which then means the extra file is not added to the rootFilesMap and then not part of the project.

Is there an alternative host configuration that should be set?