If you’re unfamiliar with TypeScript, it’s a language that brings static type-checking to JavaScript so that you can catch issues before you even run your code – or before you even save your file. It also includes the latest JavaScript features from the ECMAScript standard on older browsers and runtimes by compiling those features into a form that they understand. But beyond type-checking and compiling your code, TypeScript also provides tooling in your favorite editor so that you can jump to the definition of any variable, find who’s using a given function, and automate refactorings and fixes to common problems. TypeScript even provides this for JavaScript users (and can also type-check JavaScript code typed with JSDoc), so if you’ve used editors like Visual Studio or Visual Studio Code on a .js
file, TypeScript is powering that experience.
To get started with the language itself, check out typescriptlang.org to learn more.
But if you want to try TypeScript 3.2 out now, you can get it through NuGet or via npm by running
npm install -g typescript
You can also get editor support for
- Visual Studio 2017 (for version 15.2 or later).
- Visual Studio 2015 (which requires update 3).
- For Visual Studio Code by installing the Insiders release until the full release provides it.
- Sublime Text 3 via PackageControl.
Other editors may have different update schedules, but should all have TypeScript available soon.
We have some important information below for NuGet users and Visual Studio 2015 users, so please continue reading if you use either product.
Below we have a bit about what’s new in 3.2.
strictBindCallApply
- Object spread on generic types
- Object rest on generic types
- Node.js-based resolution for
tsconfig.json
inheritance - Diagnosing
tsconfig.json
with--showConfig
- BigInt
Object.defineProperty
declarations in JavaScript- Error message improvements
- Improved narrowing for tagged unions
- Editing improvements
- Breaking changes and deprecations
- What’s next
strictBindCallApply
As you might’ve guessed from the title of this section, TypeScript 3.2 introduces stricter checking for bind
, call
, and apply
. But what does that mean?
Well, in JavaScript, bind
, call
, and apply
are methods on functions that allow us to do things like bind this
and partially apply arguments, call functions with a different value for this
, and call functions with an array for their arguments.
Unfortunately, in its earlier days, TypeScript lacked the power to model these functions, and bind
, call
, and apply
were all typed to take any number of arguments and returned any
. Additionally, ES2015’s arrow functions and rest/spread arguments gave us a new syntax that made it easier to express what some of these methods do – and in a more efficient way as well.
Still, demand to model these patterns in a type-safe way led us to revisit this problem recently. We realized that two features opened up the right abstractions to accurately type bind
, call
, and apply
without any hard-coding:
this
parameter types from TypeScript 2.0- Modeling parameter lists with tuple types from TypeScript 3.0
Combined, the two of of them can ensure our uses of bind
, call
, and apply
are more strictly checked when we use a new flag called strictBindCallApply
. When using this new flag, the methods on callable objects are described by a new global type called CallableFunction
which declares stricter versions of the signatures for bind
, call
, and apply
. Similarly, any methods on constructable (but not callable) objects are described by a new global type called NewableFunction
.
As an example, we can look at how Function.prototype.apply
acts under this behavior:
function foo(a: number, b: string): string { return a + b; } let a = foo.apply(undefined, [10]); // error: too few argumnts let b = foo.apply(undefined, [10, 20]); // error: 2nd argument is a number let c = foo.apply(undefined, [10, "hello", 30]); // error: too many arguments let d = foo.apply(undefined, [10, "hello"]); // okay! returns a string
Needless to say, whether you do any sophisticated metaprogramming, or you use simple patterns like binding methods in your class instances (this.foo = this.foo.bind(this)
), this feature can help catch a lot of bugs. For more details, you can check out the original pull request here.
Caveats
One caveat of this new functionality is that due to certain limitations, bind
, call
, and apply
can’t yet fully model generic functions or functions that have overloads. When using these methods on a generic function, type parameters will be substituted with the empty object type ({}
), and when used on a function with overloads, only the last overload will ever be modeled.
Object spread on generic types
JavaScript supports a handy way of copying existing properties from an existing object into a new one called “spreads”. To spread an existing object into a new object, you define an element with three consecutive periods (...
) like so:
let person = { name: "Daniel", location: "New York City" }; // My secret revealed, I have two clones! let shallowCopyOfPerson = { ...person }; let shallowCopyOfPersonWithDifferentLocation = { ...person, location: "Seattle" };
TypeScript does a pretty good job here when it has enough information about the type. The type system closely tries to model the behavior of spreads and overwrites new properties, tries to ignore methods, etc. But unfortunately up until now it wouldn’t work with generics at all.
function merge<T, U>(x: T, y: U) { // Previously an error! return { ...x, ...y }; }
This was an error because we had no way to express the return type of merge
. There was no syntax (nor semantics) that could express two unknown types being spread into a new one.
We could have come up with a new concept in the type system called an “object spread type”, and in fact we had a proposal for exactly that. Essentially this would be a new type operator that looks like { ...T, ...U }
to reflect the syntax of an object spread.
When both T
and U
are known, that type would flatten down to some new object type.
However, this is pretty complex and requires adding new rules to type relationships and inference. While we explored several different avenues, we recently arrived at two conclusions:
- For most uses of spreads in JavaScript, users were fine modeling the behavior with intersection types (i.e.
Foo & Bar
). Object.assign
– a function that exhibits most of the behavior of spreading objects – is already modeled using intersection types, and we’ve seen very little negative feedback around that.
Given that intersections model the common cases, and that they’re relatively easy to reason about for both users and the type system, TypeScript 3.2 now permits object spreads on generics and models them using intersections:
// Returns 'T & U' function merge<T, U>(x: T, y: U) { return { ...x, ...y }; } // Returns '{ name: string, age: number, greeting: string } & T' function foo<T>(obj: T) { let person = { name: "Daniel", age: 26 }; return { ...person, greeting: "hello", ...obj }; }
Object rest on generic types
Object rest patterns are sort of the dual of object spreads. Instead of creating a new object with some extra/overridden properties, it creates a new object that lacks some specified properties.
let { x, y, z, ...rest } = obj;
In the above, the most intuitive way to look at this code is that rest
copies over all the properties from obj
apart from x
, y
, and z
. For the same reason as above, because we didn’t have a good way to describe the type of rest
when obj
is generic, we didn’t support this for a while.
Here we also considered a new rest operator, but we saw we already had the facilities for describing the above: our Pick
and Exclude
helper types in lib.d.ts
To reiterate, ...rest
basically picks off all of the properties on obj
except for x
, y
, and z
in the following example:
interface XYZ { x: any; y: any; z: any; } function dropXYZ<T extends XYZ>(obj: T) { let { x, y, z, ...rest } = obj; return rest; }
If we want to consider the properties of T
(i.e. keyof T
) except for x
, y
, and z
, we can write Exclude<keyof T, "x" | "y" | "z">
. We then want to pick those properties back off of the original type T
, which gives us
Pick<T, Exclude<keyof T, "x" | "y" | "z">>`.
While it’s not the most beautiful type (hey, I’m no George Clooney myself), we can wrap it in a helper type like DropXYZ
:
interface XYZ { x: any; y: any; z: any; } type DropXYZ<T> = Pick<T, Exclude<keyof T, keyof XYZ>>; function dropXYZ<T extends XYZ>(obj: T): DropXYZ<T> { let { x, y, z, ...rest } = obj; return rest; }
Configuration inheritance via node_modules
packages
For a long time TypeScript has supported extending tsconfig.json
files using the extends
field.
{ "extends": "../tsconfig-base.json", "include": ["./**/*"] "compilerOptions": { // Override certain options on a project-by-project basis. "strictBindCallApply": false, } }
This feature is very useful to avoid duplicating configuration which can easiy fall our of sync, but it really works best when multiple projects are co-located in the same respository so that each project can reference a common “base” tsconfig.json
.
But for some teams, projects are written and published as completely independent packages. Those projects don’t have a common file they can reference, so as a workaround, users could create a separate package and reference that:
{ "extends": "../node_modules/@my-team/tsconfig-base/tsconfig.json", "include": ["./**/*"] "compilerOptions": { // Override certain options on a project-by-project basis. "strictBindCallApply": false, } }
However, climbing up parent directories with a series of leading ../
s and reaching directly into node_modules
to grab a specific file feels unwieldy.
TypeScript 3.2 now resolves tsconfig.json
s from node_modules
. When using a bare path for the "extends"
field in tsconfig.json
, TypeScript will dive into node_modules
packages for us.
{ "extends": "@my-team/tsconfig-base", "include": ["./**/*"] "compilerOptions": { // Override certain options on a project-by-project basis. "strictBindCallApply": false, } }
Here, TypeScript will climb up node_modules
folders looking for a @my-team/tsconfig-base
package. For each of those packages, TypeScript will first check whether package.json
contains a "tsconfig"
field, and if it does, TypeScript will try to load a configuration file from that field. If neither exists, TypeScript will try to read from a tsconfig.json
at the root. This is similar to the lookup process for .js
files in packages that Node uses, and the .d.ts
lookup process that TypeScript already uses.
This feature can be extremely useful for bigger organizations, or projects with lots of distributed dependencies.
Diagnosing tsconfig.json
with --showConfig
tsc
, the TypeScript compiler, supports a new flag called --showConfig
. When running tsc --showConfig
, TypeScript will calculate the effective tsconfig.json
(after calculating options inherited from the extends
field) and print that out. This can be useful for diagnosing configuration issues in general.
BigInt
BigInts are part of an upcoming proposal in ECMAScript that allow us to model theoretically arbitrarily large integers. TypeScript 3.2 brings type-checking for BigInts, as well as support for emitting BigInt literals when targeting esnext
.
BigInt support in TypeScript introduces a new primitive type called the bigint
(all lowercase). You can get a bigint
by calling the BigInt()
function or by writing out a BigInt literal by adding an n
to the end of any integer numeric literal:
let foo: bigint = BigInt(100); // the BigInt function let bar: bigint = 100n; // a BigInt literal // *Slaps roof of fibonacci function* // This bad boy returns ints that can get *so* big! function fibonacci(n: bigint) { let result = 1n; for (let last = 0n, i = 0n; i < n; i++) { const current = result; result += last; last = current; } return result; } fibonacci(10000n)
While you might imagine close interaction between number
and bigint
, the two are separate domains.
declare let foo: number; declare let bar: bigint; foo = bar; // error: Type 'bigint' is not assignable to type 'number'. bar = foo; // error: Type 'number' is not assignable to type 'bigint'.
As specified in ECMAScript, mixing number
s and bigint
s in arithmetic operations is an error. You’ll have to explicitly convert values to BigInt
s.
console.log(3.141592 * 10000n); // error console.log(3145 * 10n); // error console.log(BigInt(3145) * 10n); // okay!
Also important to note is that bigint
s produce a new string when using the typeof
operator: the string "bigint"
. Thus, TypeScript correctly narrows using typeof
as you’d expect.
function whatKindOfNumberIsIt(x: number | bigint) { if (typeof x === "bigint") { console.log("'x' is a bigint!"); } else { console.log("'x' is a floating-point number"); } }
We’d like to extend a huge thanks to Caleb Sander for all the work on this feature. We’re grateful for the contribution, and we’re sure our users are too!
Caveats
As we mentioned, BigInt support is only available for the esnext
target. It may not be obvious, but because BigInts have different behavior for mathematical operators like +
, -
, *
, etc., providing functionality for older targets where the feature doesn’t exist (like es2017
and below) would involve rewriting each of these operations. TypeScript would need to dispatch to the correct behavior depending on the type, and so every addition, string concatenation, multiplication, etc. would involve a function call.
For that reason, we have no immediate plans to provide downleveling support. On the bright side, Node 11 and newer versions of Chrome already support this feature, so you’ll be able to use BigInts there when targeting esnext
.
Certain targets may include a polyfill or BigInt-like runtime object. For those purposes you may want to add esnext.bigint
to the lib
setting in your compiler options.
Object.defineProperty
declarations in JavaScript
When writing in JavaScript files (using allowJs
), TypeScript now recognizes declarations that use Object.defineProperty
. This means you’ll get better completions, and stronger type-checking when enabling type-checking in JavaScript files (by turning on the checkJs
option or adding a // @ts-check
comment to the top of your file).
// @ts-check let obj = {}; Object.defineProperty(obj, "x", { value: "hello", writable: false }); obj.x.toLowercase(); // ~~~~~~~~~~~ // error: // Property 'toLowercase' does not exist on type 'string'. // Did you mean 'toLowerCase'? obj.x = "world"; // ~ // error: // Cannot assign to 'x' because it is a read-only property.
Error message improvements
We’re continuing to push improvements in the error experience in TypeScript. Here’s a few things in TypeScript 3.2 that we believe will make the language easier to use.
- Better missing property errors (and cleaner missing attributes in JSX)
- Better error spans in arrays and arrow functions
- Error on most-overlapping types in unions (a.k.a. “pick most overlappy type”)
- Related spans when a typed
this
is shadowed - “Did you forget a semicolon?” on parenthesized expressions on the next line
- More specific messages when assigning to
const
/readonly
bindings - More accurate message when extending complex types
- Use relative module names in error messages
Thanks to Kingwl, a-tarasyuk, and prateekgoel who helped out on some of these improvements.
Improved narrowing for tagged unions
TypeScript 3.2 makes narrowing easier by relaxing rules for what’s considered a discriminant property. Common properties of unions are now considered discriminants as long as they contain some singleton type (e.g. a string literal, null
, or undefined
), and they contain no generics.
As a result, TypeScript 3.2 considers the error
property in the following example to be a discriminant, whereas before it wouldn’t since Error
isn’t a singleton type. Thanks to this, narrowing works correctly in the body of the unwrap
function.
type Either<T> = | { error: Error; data: null } | { error: null; data: T }; function unwrap<T>(result: Either<T>) { if (result.error) { // Here 'error' is non-null throw result.error; } // Now 'data' is non-null return result.data; }
Editing improvements
The TypeScript project doesn’t simply consist of a compiler/type-checker. The core components of the compiler also provide a cross-platform open-source language service that can power “smarter” editor features like go-to-definition, find-all-references, and a number of quick fixes and refactorings. TypeScript 3.2 brings some small quality of life improvements.
Quick fixes
Implicit any
suggestions and “infer from usage” fixes
We strongly suggest users take advantage of stricter checking when possible. noImplicitAny
is one of these stricter checking modes, and it helps ensure that your code is as fully typed as possible which also leads to a better editing experience.
Unfortunately it’s not all roses for existing codebases. noImplicitAny
is a big switch across codebases which can lead to a lot of error messages and red squiggles in your editor as you type code. The experience can be jarring to turn on just to find out which variables need types.
In this release, TypeScript produces suggestions for most variables and parameters that would have been reported as having implicit any
types. When an editor reports these suggestions, TypeScript also provides a quick fix to automatically infer the types for you.
This can make migrating an existing codebase to TypeScript even easier, and we expect it will make migrating to noImplicitAny
a breeze.
Going a step further, TypeScript users who are type-checking their .js
files using checkJs
or the // @ts-check
comments can now also get the same functionality with JSDoc types!
Other fixes
TypeScript 3.2 also brings two smaller quick fixes for small mistakes.
- Add a missing
new
when accidentally calling a constructor. - Add an intermediate assertion to
unknown
when types are sufficiently unrelated.
Thanks to GitHub users iliashkolyar and ryanclarke respectively for these changes!
Improved formatting
Thanks to saschanaz, TypeScript is now smarter about formatting several different constructs. Listing all of them might be a bit cumbersome, but you can take a look at the pull request here.
Breaking changes and deprecations
lib.d.ts
changes
TypeScript has recently moved more to generating DOM declarations in lib.d.ts
by leveraging IDL files provided by standards groups. Upgraders should note take note of any issues they encounter related to the DOM and report them.
More specific types
Certain parameters no longer accept null
, or now accept more specific types as per the corresponding specifications that describe the DOM.
More platform-specific deprecations
Certain properties that are WebKit-specific have been deprecated. They are likely to be removed in a new version.
wheelDelta
and friends have been removed.
wheelDeltaX
, wheelDelta
, and wheelDeltaZ
have all been removed as they are deprecated properties on WheelEvent
s.
As a solution, you can use deltaX
, deltaY
, and deltaZ
instead. If older runtimes are a concern, you can include a file called legacy.d.ts
in your project and write the following in it:
// legacy.d.ts interface WheelEvent { readonly wheelDelta: number; readonly wheelDeltaX: number; readonly wheelDeltaZ: number; }
JSX resolution changes
Our logic for resolving JSX invocations has been unified with our logic for resolving function calls. While this has simplified the compiler codebase and improved some use-cases, there may be some differences which we may need to reconcile. These changes are likely unintentional so they are not breaking changes per se, but upgraders should note take note of any issues they encounter and report them.
A note for NuGet and Visual Studio 2015
We have some changes coming in TypeScript 3.2 for NuGet and VS2015 users.
First, TypeScript 3.2 and future releases will only ship an MSBuild package, and not a standalone compiler package. Second, while our NuGet packages previously shipped with the Chakra JavaScript engine to run the compiler, the MSBuild package now depends on an invokable version of Node.js to be present. While machines with newer versions of Visual Studio 2017 (versions 15.8 and above) will not be impacted, some testing/CI machines, users with Visual Studio 2015, and users of Visual Studio 2017 15.7 and below may need to install Node.js directly from the site, through Visual Studio 2017 Build Tools (read more here), or via a redistribution of Node.js over NuGet. Otherwise, upgrading to TypeScript 3.2 might result in a build error like the following:
The build task could not find node.exe which is required to run the TypeScript compiler. Please install Node and ensure that the system path contains its location.
Lastly, TypeScript 3.2 will be the last TypeScript release with editor support for Visual Studio 2015 users. To stay current with TypeScript, we recommend upgrading to Visual Studio 2017 for the latest editing experience.
What’s next
Our next release of TypeScript is slated for the end of January. Some things we’ve got planned on the horizon are partial type argument inference and a quick fix to scaffold out declaration files that don’t exist on DefinitelyTyped. While this list is in flux, you can keep track of our plans on the TypeScript Roadmap.
We hope that TypeScript 3.2 makes your day-to-day coding more enjoyable, whether it comes to expressivity, productivity, or ease-of-use. If you’re enjoying it, drop us a line on Twitter at @typescriptlang; and if you’ve got ideas on what we should improve, file an issue on GitHub.
Happy hacking!
– Daniel Rosenwasser and the TypeScript team
The post Announcing TypeScript 3.2 appeared first on TypeScript.