Skip to content
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

Specifying supported TypeScript versions #32166

Closed
5 tasks done
ehmicky opened this issue Jun 28, 2019 · 17 comments
Closed
5 tasks done

Specifying supported TypeScript versions #32166

ehmicky opened this issue Jun 28, 2019 · 17 comments
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@ehmicky
Copy link

ehmicky commented Jun 28, 2019

Search Terms

version
minimal version
minimum version
typesVersions

Suggestion

Specifying the minimal TypeScript version supported by a project.

With typesVersions, fallbacks can be specified for earlier versions. But we cannot specify which earlier versions are not supported.

Use Cases

Better error reporting for users using older TypeScript versions.

Examples

execa types only support TypeScript >= 3.4 because of how the readonly keyword is used.

There should be a way for us to specify this. One way would be to extend the typesVersions field to add this feature.

Our users would then get a clear error message similar to the one produced by the engines.node field for Node.js versions. Instead at the moment, we get GitHub issues from users using older TypeScript versions.

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@milesj
Copy link

milesj commented Jun 28, 2019

I've been wanting this also. Maybe use engines for it also?

"engines": {
  "tsc": "^3.4.0"
}

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Jun 28, 2019

This already exists. If your package.json doesn't have a compatible TypeScript version, you'll see

"'package.json' does not have a 'typesVersions' entry that matches version '{0}'."

We decided not to backport the typesVersions to prior compiler versions, but the problem will eventually go away once everyone is on a version that supports typesVersions

@ehmicky
Copy link
Author

ehmicky commented Jun 28, 2019

@RyanCavanaugh this is great! However this does not seem to work for me. Steps to reproduce:

$ npm install execa

Manually edit (in node_modules/execa/package.json) the execa package.json to add support only for a future version of TypeScript:

  "typesVersions": {
    ">=4": { "*": ["index.d.ts"] }
  }

Then using index.ts:

import 'execa'

This creates no errors:

$ tsc index.ts

TypeScript 3.5.2, node 12.5.0, Ubuntu 19.04. No tsconfig.json.

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Jun 28, 2019
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.6.0 milestone Jun 28, 2019
@RyanCavanaugh
Copy link
Member

Just to check while you have it set up, does this happen if index.d.ts is named something else? I think we may have some fallback logic

@ehmicky
Copy link
Author

ehmicky commented Jun 28, 2019

If I rename index.d.ts to main.d.ts, tsc still produces no output. I tried renaming index.js to main.js as well.

With the original name, the --traceResolution output is:

======== Resolving module 'execa' from '/home/ether/Desktop/index.ts'. ========
Module resolution kind is not specified, using 'NodeJs'.
Loading module 'execa' from 'node_modules' folder, target file type 'TypeScript'.
Found 'package.json' at '/home/ether/Desktop/node_modules/execa/package.json'.
'package.json' has a 'typesVersions' field with version-specific path mappings.
'package.json' does not have a 'typesVersions' entry that matches version '3.5'.
File '/home/ether/Desktop/node_modules/execa.ts' does not exist.
File '/home/ether/Desktop/node_modules/execa.tsx' does not exist.
File '/home/ether/Desktop/node_modules/execa.d.ts' does not exist.
'package.json' does not have a 'typings' field.
'package.json' does not have a 'types' field.
'package.json' does not have a 'main' field.
File '/home/ether/Desktop/node_modules/execa/index.ts' does not exist.
File '/home/ether/Desktop/node_modules/execa/index.tsx' does not exist.
File '/home/ether/Desktop/node_modules/execa/index.d.ts' exist - use it as a name resolution result.
Resolving real path for '/home/ether/Desktop/node_modules/execa/index.d.ts', result '/home/ether/Desktop/node_modules/execa/index.d.ts'.
======== Module name 'execa' was successfully resolved to '/home/ether/Desktop/node_modules/execa/index.d.ts' with Package ID 'execa/index.d.ts@2.0.0'. ========

It does fallback to index.d.ts (which means minimal TypeScript version is not enforced). This is the output when renaming files:

======== Resolving module 'execa' from '/home/ether/Desktop/index.ts'. ========
Module resolution kind is not specified, using 'NodeJs'.
Loading module 'execa' from 'node_modules' folder, target file type 'TypeScript'.
Found 'package.json' at '/home/ether/Desktop/node_modules/execa/package.json'.
'package.json' has a 'typesVersions' field with version-specific path mappings.
'package.json' does not have a 'typesVersions' entry that matches version '3.5'.
File '/home/ether/Desktop/node_modules/execa.ts' does not exist.
File '/home/ether/Desktop/node_modules/execa.tsx' does not exist.
File '/home/ether/Desktop/node_modules/execa.d.ts' does not exist.
'package.json' does not have a 'typings' field.
'package.json' does not have a 'types' field.
'package.json' does not have a 'main' field.
File '/home/ether/Desktop/node_modules/execa/index.ts' does not exist.
File '/home/ether/Desktop/node_modules/execa/index.tsx' does not exist.
File '/home/ether/Desktop/node_modules/execa/index.d.ts' does not exist.
File '/home/ether/Desktop/node_modules/@types/execa.d.ts' does not exist.
Directory '/home/ether/node_modules' does not exist, skipping all lookups in it.
Directory '/home/node_modules' does not exist, skipping all lookups in it.
Directory '/node_modules' does not exist, skipping all lookups in it.
Loading module 'execa' from 'node_modules' folder, target file type 'JavaScript'.
Found 'package.json' at '/home/ether/Desktop/node_modules/execa/package.json'.
'package.json' has a 'typesVersions' field with version-specific path mappings.
'package.json' does not have a 'typesVersions' entry that matches version '3.5'.
File '/home/ether/Desktop/node_modules/execa.js' does not exist.
File '/home/ether/Desktop/node_modules/execa.jsx' does not exist.
'package.json' does not have a 'main' field.
File '/home/ether/Desktop/node_modules/execa/index.js' does not exist.
File '/home/ether/Desktop/node_modules/execa/index.jsx' does not exist.
Directory '/home/ether/node_modules' does not exist, skipping all lookups in it.
Directory '/home/node_modules' does not exist, skipping all lookups in it.
Directory '/node_modules' does not exist, skipping all lookups in it.
======== Module name 'execa' was not resolved. ========

It does not resolve execa but does not create any error messages.

@rbuckton
Copy link
Member

Here's one thing you can try:

  1. Add a notsupported.d.ts file to execa.
  2. Modify the package.json for execa in the following way:
"types": "notsupported.d.ts",
"typesVersions": {
  ">=3.4.0-0": { "*": ["index.d.ts"] }
}

TypeScript versions earlier than 3.4 will load notsupported.d.ts which is empty. TypeScript versions for 3.4 onward will load index.d.ts. Attempting to import {} from 'execa' will report an error since notsupported.d.ts is not a module. However, import 'execa' will still work without error because this import form only imports the referenced file for side-effects and does not care whether the referenced file is a module.

@ehmicky
Copy link
Author

ehmicky commented Jun 29, 2019

Thanks for this tip @rbuckton! It works but some issues with this approach could be:

  • the error message doesn't make it clear for the user that they are using an unsupported TypeScript version: File '/home/ether/Desktop/node_modules/execa/notsupported.d.ts' is not a module.. Many users will probably end up still creating GitHub issues thinking there's something wrong with the types declarations.
  • it requires creating an empty file, although that's not a big issue.

More generally speaking this would be more of a workaround. Most TypeScript projects probably have a minimal supported TypeScript version, so it might be beneficial to have a "standard" way to specify it. This could also enable tools (linters, static analysis tools) to use it, in the same way that engines.node is currently used for Node.js versions.

@rbuckton
Copy link
Member

rbuckton commented Aug 7, 2019

If we were to add a feature to disallow older versions of TypeScript, it wouldn't catch TS versions prior to the version where the feature was introduced.

It's not pretty, but another workaround would be to introduce a type error that could provide some context to the users that the TS version they are using isn't supported. Something like:

// notsupported.d.ts
type __Check<T extends ">= 3.6.1"> = T;
type __NotSupported = __Check<"This version of TypeScript">;
// error: Type '"This version of TypeScript"' does not satisfy the constraint '">= 3.6.1"'.

@ehmicky
Copy link
Author

ehmicky commented Aug 7, 2019

Thanks @rbuckton! That does provide with a better error message, so this seems like the best workaround at the moment.

Any feature related to this issue would indeed only work for users whose TS version is >= to the version that introduced that feature. I think this might still be a good idea even if it would take some time to be useful (since it requires most TypeScript users to migrate to at least that TS version first, which might take years).

As a side note, I think the following might work as well, as a slight variation of the workaround above, maybe more explicit:

"typesVersions": {
  ">=3.4.0-0": { "*": ["index.d.ts"] },
  "*": { "*": ["notsupported.d.ts"] }
}

@rbuckton
Copy link
Member

rbuckton commented Aug 9, 2019

Keep in mind that won't catch TypeScript 3.0 and earlier

@rbuckton
Copy link
Member

rbuckton commented Aug 9, 2019

We are considering a mechanism to indicate an unsupported TypeScript version (with the caveat that earlier versions won't support this capability), but there is not enough time this iteration to implement it.

The change might be something like this:

"typesVersions": {
  ">=3.4.0-0": { "*": ["index.d.ts"] },
  "*": "not-supported"
}

@yacinehmito
Copy link
Contributor

What happens if I do the opposite on my project? By default expose type declarations with a type error, and override it with the proper file for versions above 3.4.0?
Will that error for versions below 3.4.0 and behave properly for 3.4.0 and above?

@RyanCavanaugh
Copy link
Member

This can has been getting kicked down the road for quite a while and it seems like this isn't enough of a problem in practice to warrant a capital-f Feature for it. It's pretty straightforward at this point to have a pre-minumum-tagged file with the content

// @ts-expect-error
type RequiredTypeScriptVersion = "3.8";

which would be sufficient for people to figure out what's going on.

@ehmicky
Copy link
Author

ehmicky commented Feb 4, 2022

I understand. Thanks @RyanCavanaugh for explaining the rational behind this decision and for providing a workaround. 👍

@smaye81
Copy link

smaye81 commented Aug 12, 2022

Has anyone implemented this approach? I can't seem to get it to officially work and wanted to see a working example if anyone has one.

@IvanGoncharov
Copy link

Has anyone implemented this approach? I can't seem to get it to officially work and wanted to see a working example if anyone has one.

@smaye81 I implemented it in graphql-js, see here:
https://github.com/graphql/graphql-js/blob/743f42b6ef6006d35bf9e0b45e3b70d6e9100596/resources/build-npm.ts#L86-L97
It produces an error like this:

node_modules/graphql/NotSupportedTSVersion.d.ts:1:63 - error TS1003: Identifier expected.

1 "Package 'graphql' support only TS versions that are >=4.4.0".

@smaye81
Copy link

smaye81 commented Aug 16, 2022

Has anyone implemented this approach? I can't seem to get it to officially work and wanted to see a working example if anyone has one.

@smaye81 I implemented it in graphql-js, see here: https://github.com/graphql/graphql-js/blob/743f42b6ef6006d35bf9e0b45e3b70d6e9100596/resources/build-npm.ts#L86-L97 It produces an error like this:

node_modules/graphql/NotSupportedTSVersion.d.ts:1:63 - error TS1003: Identifier expected.

1 "Package 'graphql' support only TS versions that are >=4.4.0".

Great thank you @IvanGoncharov !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

9 participants