Typescript: The TypeScript 3.1 "typesVersions" setting in package.json should use an array or disallow version overlaps

Created on 28 Sep 2018  路  4Comments  路  Source: microsoft/TypeScript

Search Terms

typesVersions, 3.1

Problem

Using an unordered object for the typesVersion setting in package.json is brittle as the implementation depends on the behavior of JSON.parse and how keys in an object are iterated over.

FWIW, using an object made sense before version ranges support was added to typesVersion.

Suggestion

Change typesVersions setting package.json to use an (ordered) array of configurations rather than an (unordered) object. For example:

{
  "typesVersions": [{
    "version": ">=3.2",
    "paths": { "*": ["ts3.2/*"] }
  }, {
    "version": ">=3.1",
    "paths": { "*": ["ts3.1/*"] }
  }
  ]
}

Alternatively, you could keep the current format, but remove the ambiguity by disallowing overlapping version matches. So the example in the 3.1 docs would be invalid and treated as error when compiling under TypeScript 3.2. A corresponding valid configuration would look like this:

"typesVersion": {
  // NOTE: order doesn't matter :-)
  ">=3.1 <3.2": { "*": ["ts3.1/*"] },
  ">=3.2": { "*": ["ts3.2/*"] }
}

This would require getPackageJsonTypesVersionsPaths in src/compiler/moduleNameResolver.ts to check all the typesVersion configs and throw an error if there is more than one match for the current TypeScript version.

Backwards Compatibility

For backwards compatibility of an array configuration, the 3.1 object configuration could still be supported, but should be deprecated.

More Info

The JSON specification says that:

An object is an unordered set of name/value pairs.

However, the typesVersions setting in TypeScript 3.1 treats it as an order set. From the docs:

Since ranges have the potential to overlap, determining which redirect applies is order-specific. That means in the above example, even though both the >=3.2 and the >=3.1 matchers support TypeScript 3.2 and above, reversing the order could have different behavior, so the above sample would not be equivalent to the following.

{
  "name": "package-name",
  "version": "1.0",
  "types": "./index.d.ts",
  "typesVersions": {
    // NOTE: this doesn't work!
    ">=3.1": { "*": ["ts3.1/*"] },
    ">=3.2": { "*": ["ts3.2/*"] }
  }
}

Use Cases

  • Removes dependency on undocumented object key iteration order
  • Won't won't be affected by someone sorting their package.json keys (and subkeys).

Checklist

My suggestion meets these guidelines:

  • [x] This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. new expression-level syntax)
Won't Fix

All 4 comments

I'd be happy to submit a PR if the suggestion is accepted and there is consensus on the configuration format.

the implementation depends on the behavior of JSON.parse and how keys in an object are iterated over.

While this is technically true, every runtime in use today returns them in a deterministic and predictable (file) order. Even if that weren't the case, we could parse the file ourselves if somehow the runtimes moved out from under us and we were the only project in existence to get broken by a key enumeration change (not happening)

we could parse the file ourselves if somehow the runtimes moved out from under us

In the JSON specification, object properties are explicitly unordered. If you propose parsing the file yourself using different rules, then you are saying that tsconfig is no longer a JSON file, since its meaning cannot always be obtained correctly using a standard JSON parser. This also sounds like a maintainability nightmare, since everyone is going to assume that it's JSON (when it isn't exactly), and therefore assume that the ordering cannot matter. This may be particularly true if there are third party tools that may want to read and write the file for whatever reason and will assume JSON.

If you rely on an existing JSON parser then you should not rely on it producing objects in your expected order unless the JSON specification is changed to accommodate ordering.

It's also worth noting that if the JSON file is read as a JavaScript object, the same ordering issues can sometimes apply to JavaScript objects, depending on what version of ES, and depending on what you use to iterate it. Relying on the ordering of JavaScript object keys doesn't seem like a good idea.

every runtime in use today returns them in a deterministic and predictable (file) order

Can you really ensure that in all situations, on all runtimes? Maybe when the object gets past a certain size, it gets reallocated as a hash map instead of a flat structure in the internal implementation, and the ordering you expected gets lost. Even if this doesn't happen today, it could happen tomorrow.

But rather than trying to predict all the parties involved (engines, tools, users) that may be expecting/relying on it being exactly JSON (where order can't matter), I think it's much better to just make it correct to start with, using one of the proposed solutions.

Enumeration order has been formalized: https://github.com/tc39/proposal-for-in-order

Was this page helpful?
0 / 5 - 0 ratings

Related issues

yortus picture yortus  路  157Comments

fdecampredon picture fdecampredon  路  358Comments

disshishkov picture disshishkov  路  224Comments

tenry92 picture tenry92  路  146Comments

quantuminformation picture quantuminformation  路  273Comments