If you haven’t heard of TypeScript, it’s a language that builds on top of the most up-to-date versions of JavaScript by adding optional static types. These types don’t just help catch things like typos and logic errors; they also can bring you even better tooling like editor completions, easier navigation of your codebase, and more. Best of all, you don’t always have to write out your type annotations – TypeScript can often infer them for you!
To learn more about TypeScript itself, you can visit our website, but if you’re already familiar with it and want to try out the release, we have it available over NuGet or through npm using the following command:
npm install -g typescript
Visual Studio 2015 users (who have Update 3) can install TypeScript 2.6 from here, and Visual Studio 2017 users using version 15.2 or later will be able to get TypeScript by simply installing it from here. Visual Studio 2017 users should be sure to read up on how you can configure your project to target specific versions of TypeScript.
TypeScript 2.6 will be available for other editors soon. In the meantime you can configure Visual Studio Code and Sublime Text to use a newer version. Other editors may have different approaches to getting TypeScript 2.6 running.
Let’s see what’s ready today in TypeScript 2.6!
Contravariant parameter types with --strictFunctionTypes
When comparing signatures – things that make your types callable or constructable – TypeScript has to account for both the return types and the parameter types.
Return types are easy – for a function f
to be assignable to g
, f
‘s return type has to be assignable to g
‘s return type. The fact that the directionality doesn’t change in this comparison is called covariance.
However, parameters are actually a different story – the correct approach is to go in the opposite direction! To see why, let’s take a quick example where we assign the below function g
to f
:
interface Animal { animalProp: any };
interface Dog extends Animal { dogProp: any };
let f = (animal: Animal) => animal.animalProp;
let g = (dog: Dog) => dog.dogProp;
// Should this succeed?
f = g;
At a glance, we might be tempted to say that since Dog
is assignable to Animal
, g
is assignable to f
, but that’s not the case. It becomes clear once we ask the right questions about substitutability:
- Is it okay for a value of type
(dog: Dog) => any
to say it can be used in place of a(animal: Animal) => any
?- Is it okay to say my function only expects an
Animal
when it may use properties that onDog
?- Only if an
Animal
can be used in place of aDog
– so isAnimal
assignable toDog
?- No! It’s missing
dogProp
.
- No! It’s missing
- Only if an
- Is it okay to say my function only expects an
So g
is not assignable to f
, but is the opposite true?
- Is it okay for a value of type
(animal: Animal) => any
to say it can be used in place of a(dog: Dog) => any
?- Is it okay to say my function expects a
Dog
when it may use properties that onAnimal
?- Only if a
Dog
can be used in place of anAnimal
– so isDog
assignable toAnimal
?- Yes!
- Only if a
- Is it okay to say my function expects a
Notice that in asking if (animal: Animal) => any
is assignable to (dog: Dog) => any
, we end up asking whether Dog
is assignable to Animal
. This flip in direction is called contravariance.
While this approach is appropriate in most languages, it’s difficult to reconcile it with the way that JavaScript is broadly used. Things like using arrays and describing methods in the HTML DOM hierarchy turn out to be problematic with strict contravariance. For instance, in the Array<T>
type, its pop
method returns a T
and its push
method takes a T
. If TypeScript compared every function parameter contravariantly, it would make all Array
s invariant on T
since T
occurs in both covariant and contravariant positions. In other words, Array<Dog>
wouldn’t be assignable to Array<Animal>
, which could be challenging to work around for many scenarios.
That’s why TypeScript compares parameters bivariantly or bidirectionally. It means completely unrelated types will get caught, but it means that certain unsavory issues can fall through the cracks when there is enough overlap:
function makeLowerCase(s: string) {
return s.toLowerCase();
}
declare let foo: Promise<string | number>;
foo.then(makeLowerCase); // Whoops! TypeScript allows this, but `makeLowerCase` might get a `number`.
That’s why in TypeScript 2.6, we’re bringing users a way to tighten things up with --strictFunctionTypes
Under this new --strict
mode flag, any function type that doesn’t originate from a method has its parameters compared strictly contravariantly.
That means that the above code will fail, because when we try to pass makeLowerCase
with the type (s: string) => string
to then
which expects a function of type (onfulfilled: string | number) => ...
, we’ll flip directions and try to assess whether string | number
is assignable to string
(which isn’t the case – string
is a subtype of string | number
). Excluding methods from the check allows TypeScript to continue modeling the above use-cases (e.g. event handlers and simpler array handling) while still bringing this much-demanded strictness check.
Covariance and contravariance probably deserve a more thorough explanation. If you’d like to read a bit more, Stephan Boyer has an approachable article that gives a reasonable high-level explanation. You can also read up more on the original pull request. The short version is that with --strictFunctionTypes
, you’ll be able to catch many common mistakes.
Keep in mind that while --strictFunctionTypes
is opt-in, it will automatically be turned on if you’re operating with the --strict
flag on. This may introduce some breaks, so to disable the check with --strict
on, you can specify --strictFunctionTypes false
on the command line or in your tsconfig.json
as so:
{
"compilerOptions": {
"strict": true,
"strictFunctionTypes": false
}
}
Translated tsc
via the --locale
flag
The standalone TypeScript compiler now provides localized (translated) messages over npm when using the --locale
flag. Simply pass the appropriate language tag as an argument to the TypeScript compiler’s --locale
option. If the language is supported, TypeScript will provide a translated version, and fall back to
This means that you can get messages
in Chinese (Simplified with zh-CN
and Traditional with zh-TW
):
tsc --pretty --locale zh-CN bar.ts(1,1): error TS2362: 算术运算左侧必须是 "any"、、"number" 或枚举类型。 'hello' * 1 ~~~~~~~
in Spanish:
$ tsc --pretty --locale es
foo.ts(1,1): error TS2362: La parte izquierda de una operación aritmética debe ser de tipo "any", "number" o un tipo enum.
'hello' * 1
~~~~~~~
in Japanese:
$ tsc --pretty --locale ja
foo.ts(1,1): error TS2362: 算術演算の左辺には、'any' 型、'number' 型、または列挙型を指定してください。
'hello' * 1
~~~~~~~
in Russian:
$ tsc --pretty --locale ru
foo.ts(1,1): error TS2362: Левый операнд арифметической операции должен иметь тип any, number или тип перечисления.
'hello' * 1
~~~~~~~
and others that you’ll be able to read about on our compiler options page.
Faster --watch
mode
TypeScript’s --watch
mode now acts much more incrementally when emitting modules. Given a set of changed files, tsc
will now figure out affected set of files for which a change might be impactful. This means that only that impacted files will undergo a tree transform pass (the process of transforming code from TypeScript to ES2016 to ES2015 to ES5 to ES3), as well as the emit pass (printing out the transformed files themselves). For extremely large codebases that heavily use ECMAScript modules, this can make a significant difference.
If you’re not using --watch
mode because you rely on another build tool, the good news is that we intend to provide other tools with an API so that they can also get some of the same performance wins from this change. Tools that plug TypeScript into Webpack, Gulp, and others might be able to leverage this API, and we’re hoping to deliver it in one of our near future releases.
Error suppression comments with // @ts-ignore
Historically, we’ve avoided error suppression within TypeScript because most cases where users have asked for it could be solved through more accurate declaration files or using a type assertion to any
. However, over time, we have seen two motivating examples: migrating from JavaScript to TypeScript, and overcoming type-checks that live in legacy code.
With TypeScript 2.6 we’re bringing // @ts-ignore
comments to TypeScript files. These comments are a light-weight way to suppress any error that occurs on the next line. For example, in the following code, TypeScript would ordinarily report an error about the console.log
statement being unreachable. In TypeScript 2.6, the // @ts-ignore
comment squelches the error entirely.
if (false) {
// @ts-ignore: Unreachable code error
console.log("hello");
}
While this feature is motivated by pragmatism, we encourage taking type-checking errors seriously. Our guidance here is to use suppression comments very sparingly. In situations where you do need to use these comments, we suggest leaving a trailing explanation of why the comment is necessary like in the example above.
Improved tooling support
Our investment in TypeScript doesn’t only involve advancing the language and compiler. Improving the language service is core to the TypeScript experience. Here’s a few improvements you’ll see soon in editors like Visual Studio and Visual Studio Code.
Quick fixes for implicit any
s
TypeScript can now look at use-sites to infer types for declarations whose types are implicitly any
.
Refactor JSDoc to TypeScript annotations
TypeScript now provides a refactoring to add parameter annotations from JSDoc comments.
When migrating from an older JavaScript codebase, you can use this refactoring together with the implicit any
quick fix to type your codebase even faster.
Invoke uncalled decorators
Occasionally you might try to use a decorator without calling it first.
Thankfully, TypeScript can use some basic heuristics to figure these scenarios out, and can provide a useful error message with a handy quick-fix to correct from something like @Input
to @Input()
.
Auto-install from @types
Editors will soon be able to provide a quick fix to install type declarations for untyped imports!
Breaking Changes and Deprecations
There are several minor changes that may impact your codebase. While you can read up more about them in our Breaking Changes section, please keep these in mind when upgrading.
- Write only references are now considered unused under
--noUnusedLocals
and--noUnusedParameters
. - In ambient contexts (e.g. declaration files, and
declare module
blocks), expressions are now disallowed indefault
exports. - Uninhabitable types resulting from intersections (
number & string
,"foo" & 42
, etc.) will simplify tonever
when placed in a union. - Various organizational changes have been made to DOM declarations in
lib.d.ts
.
As for deprecations, the getSymbolDisplayBuilder
API is now deprecated. For simple use cases, you should be able to move to TypeChecker#symbolToString
. For more advanced use cases, we plan to bring symbolToString
fully up to par with SymbolDisplayBuilder
in functionality by TypeScript 2.7. In a release following TypeScript 2.7, we will be be removing getSymbolDisplayBuilder
.
What’s next?
To get a more complete picture of what’s in TypeScript 2.6, you can check out our What’s New in TypeScript wiki page. You can also see what we’ve got coming up in our release schedule on the TypeScript Roadmap.
It should go without saying, but let us know if you run into any problems on our issue tracker. And if you’re enjoying this release, let us know on Twitter by using the #iHeartTypeScript hashtag.
We hope that this version of TypeScript is easy to adopt, brings even more type safety, makes you more productive, and is just plain fun to use.
Happy Hacking!
0 comments