-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Configuration Profiles #50997
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
Comments
@RyanCavanaugh I have a question How is this different from It seems to satisfy
|
Good question. One key difference is that we can't automatically insert that line into every existing tsconfig.json, so if we ever want to change TS's default settings (which we certainly do), we don't really have any other way to do that. The other problem is that an upstream config file can't exactly mimic all behavior from various settings, since some settings behave differently if they're explicitly specified vs left blank (due to depending on other settings). For example, today (if not otherwise specified) |
It's a very good idea - to keep compatibility with old codebases and tons of existing code. But proposed implementation is kind of anti-pattern: it increases complexity of existing thing already has a lot of issues. Examples? 'Extend' in tsconfig, project references, 'exports / imports' in package.json, old-style eslint config, etc. If you really want to improve ts ecosystem, just take looks at something like ts.config.js / .ts with some default / recommended packages / presets / codemods, and freeze current state of tsconfig. |
TypeScript Configuration Profiles
The Problem
Many times when improving correctness or allowing for increased strictness, we (the TypeScript team) face some challenging questions:
Let's look at a concrete example to understand how these factors play out.
Unconstrained Type Parameter /
{ }
assignabilityIn TypeScript 4.8, we fixed a longstanding issue that unconstrained generic type parameters were unsoundly assignable to
{ }
. Critically, this was a correctness fix -- in any situation, there was the potential for a bug in user code to be hiding.In a new codebase, there is zero reason to allow those sorts of incorrect assignments to occur. You would easily add the needed constraint and/or
if
check. In a sense, this fix is really a subset of the behaviors implied bystrictNullChecks
-- thatnull
andundefined
are not allowed to inhabit types that don't declare them.However, this check could (and did) cause errors in declaration files. This is a problem, because not all declaration files are necessarily under your control. Types shipped within an npm module, for example, cannot be easily overriden, and types in those modules could be declared in a way that caused errors. For 4.8, we did a great deal of legwork to find and fix these issues, as well as in DefinitelyTyped.
In existing codebases, the odds of hitting at least one new error due to this change were reasonably high. But in practice, most unconstrained type parameters are rarely inhabited by
null
orundefined
, and in some cases we saw type parameters that were only ever referenced as instantiations using properly-constrained type parameters. For the most part, these codebases didn't have lurking bugs. While the possibility ofnull
/undefined
sneaking in is always present, we only saw a handful of cases where a true bug was found.This combination of factors created a difficult situation:
We went back and forth, a lot, over whether to add a flag to control this behavior. The key decision points were:
strict
flag, and should be on by default, but doing this creates the low-value breaks described aboveOn net, we decided to live with the breaks and ship this without a flag. The reception to this has been largely positive, but it was unsatisfying to ship this kind of change. We want upgrading TypeScript to be a gratifying process that makes you think "Ah yes, I see an older version of TypeScript missed that problem", not "This new version of TypeScript is complaining about something I never had to think about before".
That said, we know this experience wasn't optimal, and we had to do a lot of work to ship a change like this without being overly disruptive.
The Strictness Dilemma
Working from this example, we basically see four kinds of "new errors" you might encounter today:
noUncheckedIndexedAccess
- while clearly more sound, we think this flag has a high "noise floor" that isn't necessarily broadly-desirable{ }
assignability falls into this category.strictPropertyInitialization
can usually be turned on without too much effort, and often finds bugs in the process.strictNullChecks
- it'd be a very odd choice to start a new project in 2022 with this flag disabled, but enabling this flag for an existing project is known to be an extremely large amount of workOpinionated checks are easy to reason about: These don't need to be on by default, ever.
Bugfix checks are also usually easy to reason about: Bug fixes are bug fixes and all software can fix bugs in a way that is observable during the upgrade process. In rare cases, like the unconstrained type parameters, bug fix checks can result in an unusually high number of new errors in codebases. When practical, we can consider a migration flag that is deprecated during the next deprecation wave.
"Easy good checks" present a small problem: Users, especially those upgrading by multiple releases at once, can be overwhelmed by the quantity of new errors they see. Even a dozen errors can feel like an enormous task if you're not familiar with the code. While these flags can be disabled, it's not always clear which errors are due to which flag, and the complaint of "I didn't ask for this in a minor version" is a reasonable one.
"Hard good checks" present a big problem: We want these checks to be in the
strict
family, but as most users enablestrict
(which we want them to do!), turning them on by default risks presenting a huge wall of errors to people trying to do a routine TypeScript version update.What can be done?
Configuration Profiles
Let's imagine a new compiler option,
profile
:What is it?
profile
is a setting that changes the default values of all other config options.By providing a
profile
value, we can allow TypeScript users to "lock in" their current compiler options regardless of versioning, while still allowing an empty tsconfig to have as few explicitly-specified properties as possible to get the "best" TypeScript experience. This also lets us reduce the confusing interaction between various settings -- for example, todaystrict
also turns onnoImplicitAny
, and there are other dependencies that are more complex.With
profile
,tsc --init
can produce a much more minimal file, containing"profile": "whatever the newest one is"
, plus commented-out lines for opinion-based settings likenoUncheckedIndexedAccess
. We can also expect existingtsconfig.json
files to get much shorter, since they can quickly opt in to their current settings modulo any overrides needed.Scheduling
New profiles can be created on an as-needed basis. During major version upgrades, we can upgrade the default profile to whatever the latest is at the time.
At the 5.0 release, the default
profile
will correspond to compiler settings as they were computed in TypeScript 4.9. At the 6.0 release, we'll "upgrade" the versioning policy, and at that time the default profile will be whatever the newest profile at that time is. This gives people plenty of time to lock in a profile in the meantime to avoid unwanted surprises.What It Isn't, and Naming
profile
changings the default compiler options. Critically, it has no other effect.Some compilers, like C#, have a "compiler version" setting. This setting lets newer compiler versions "act like" older versions, to the greatest extent feasible. This is not what
profile
is intended to be. Given the surrounding ecosystem, it's reasonably straightforward to install any specific TypeScript version on a per-project basis. For this reason, we don't want to name this in a way that implies it is a version selection mechanism. While the profiles will obvious correspond to a TypeScript version in which they were first present, these profiles aren't intended to allow for mimicing of older versions.This is also not intended to be a "strictness dial". Our intent when we first made
--strict
was that--strict
would always opt you in to the very strictest TypeScript settings. It quickly became apparent that people really wanted to opt themselves into this mode, and that projects with no appetite to incur large breaky changes on version upgrades. If we useprofile
and start creating options like"strict"
,"strictier"
,"stricest"
,"strictest-latest"
, etc, we're putting ourselves back into the boat of having to figure out which changes are palatable to existing codebases and which aren't. The preferred mechanism for opting into a well-known configuration with some settings is toextend
from a shipping package containing TypeScript defaults.Given these constraints, we'd like some feedback on how to name both the setting itself, and its allowed values.
The text was updated successfully, but these errors were encountered: