TypeScript 3.7 has Been Released

TypeScript 3.7 has Been Released

The TypeScript team announced the release of TypeScript 3.7, including optional chaining, nullish coalescing, assertion functions

Recently, Microsoft announced the latest version of TypeScript i.e. 3.7. The new release brings new language, compiler, and tooling features.

Well, TypeScript is a language based on JavaScript that provides static type-checking along with type syntax. It powers things like completions, quick fixes, and refactorings for both TypeScript and JavaScript in many of the editors.

You can get the latest version through NuGet, or using npm - npm install typescript

3.7 brings some great features like Optional Chaining, Nullish Coalescing, Better Support for never-Returning Functions, and Assertion Signatures

You can check out the TypeScript playground where you will find an entire menu for learning what’s new.

Source: Microsoft

New feature Optional chaining allows you to write code where you can immediately stop running some expressions if you run into a null or undefined. Consider the following code:

let x = foo?.bar.baz();  

that means when foo is defined, foo.bar.baz() will be computed, but incase foo is null or undefined, it will stop and return undefined.

And, the nullish coalescing operator(??) is a feature that goes hand-in-hand with optional chaining. ?? operator is a way to fall-back to a default value when dealing with null or undefined. So if you write a code like:

let x = foo ?? bar();  

it means the value foo will be used when it’s present; but if it is null or undefined, it will calculate bar() in its place.

TypeScript 3.7 brings a new concept - assertion signatures - which model the assertion functions.

Here, one type of assertion signature models the way that Node’s assert function works. It makes sure that whatever condition is being checked must be true for the remainder of the containing scope. While, the other type of assertion signature, instead of checking for a condition, tells TypeScript that a specific variable or property has a different type.

Other important features, included in 3.7 are:

  • --declaration and --allowJs
  • (More) Recursive Type Aliases
  • The useDefineForClassFields Flag and The declare Property Modifier
  • Build-Free Editing with Project References
  • Uncalled Function Checks
  • Flatter Error Reporting
  • // @ts-nocheck in TypeScript Files
  • Semicolon Formatter Option
  • Website and Playground Updates

_And breaking changes include DOM Changes, Class Field Mitigations, Local and Imported, _Type Declarations Now Conflict, Function Truthy Checks, and API Changes

You can visit the official announcement here.

Thank you !

Announcing TypeScript 3.7 Beta

Announcing TypeScript 3.7 Beta

We’re pleased to announce TypeScript 3.7 Beta, a feature-complete version of TypeScript 3.7. Between now and the final release, we’ll be fixing bugs and further improving performance and stability.

To get started using the beta, you can get it through NuGet, or use npm with the following command:

npm install [email protected]

You can also get editor support by

TypeScript 3.7 Beta includes some of our most highly-requested features! Let’s dive in and see what’s new, starting with the highlight feature of 3.7: Optional Chaining.

Optional Chaining

TypeScript 3.7 implements one of the most highly-demanded ECMAScript features yet: optional chaining! Our team has been heavily involved in TC39 to champion the feature to Stage 3 so that we can bring it to all TypeScript users.

So what is optional chaining? Well at its core, optional chaining lets us write code where we can immediately stop running some expressions if we run into a null or undefined. The star of the show in optional chaining is the new ?. operator for optional property accesses. When we write code like

let x = foo?.bar.baz();

this is a way of saying that when foo is defined, foo.bar.baz() will be computed; but when foo is null or undefined, stop what we’re doing and just return undefined.”

More plainly, that code snippet is the same as writing the following.

let x = (foo === null || foo === undefined) ?
    undefined :
    foo.bar.baz();

Note that if bar is null or undefined, our code will still hit an error accessing baz. Likewise, if baz is null or undefined, we’ll hit an error at the call site. ?. only checks for whether the value on the left of it is null or undefined – not any of the subsequent properties.

You might find yourself using ?. to replace a lot of code that performs intermediate property checks using the && operator.

// Before
if (foo && foo.bar && foo.bar.baz) {
    // ...
}

// After-ish
if (foo?.bar?.baz) {
    // ...
}

Keep in mind that ?. acts differently than those && operations since && will act specially on “falsy” values (e.g. the empty string, 0, NaN, and, well, false).

Optional chaining also includes two other operations. First there’s optional element access which acts similarly to optional property accesses, but allows us to access non-identifier properties (e.g. aribtrary strings, numbers, and symbols):

/**
 * Get the first element of the array if we have an array.
 * Otherwise return undefined.
 */
function tryGetFirstElement<T>(arr?: T[]) {
    return arr?.[0];
    // equivalent to
    //   return (arr === null || arr === undefined) ?
    //       undefined :
    //       arr[0];
}

There’s also optional call, which allows us to conditionally call expressions if they’re not null or undefined.

async function makeRequest(url: string, log?: (msg: string) => void) {
    log?.(`Request started at ${new Date().toISOString()}`);
    // equivalent to
    //   if (log !== null && log !== undefined) {
    //       log(`Request started at ${new Date().toISOString()}`);
    //   }

    const result = (await fetch(url)).json();

    log?.(`Request finished at at ${new Date().toISOString()}`);

    return result;
}

The “short-circuiting” behavior that optional chains have is limited to both “ordinary” and optional property accesses, calls, element accesses – it doesn’t expand any further out from these expressions. In other words,

let result = foo?.bar / someComputation()

doesn’t stop the division or someComputation() call from occurring. It’s equivalent to

let temp = (foo === null || foo === undefined) ?
    undefined :
    foo.bar;

let result = temp / someComputation();

That might result in dividing undefined, which is why in strictNullChecks, the following is an error.

function barPercentage(foo?: { bar: number }) {
    return foo?.bar / 100;
    //     ~~~~~~~~
    // Error: Object is possibly undefined.
}

More more details, you can read up on the proposal and view the original pull request.

Nullish Coalescing

The nullish coalescing operator is another upcoming ECMAScript feature that goes hand-in-hand with optional chaining, and which our team has been deeply involved in championing.

You can think of this feature – the ?? operator – as a way to “fall back” to a default value when dealing with null or undefined. When we write code like

let x = foo ?? bar();

this is a new way to say that the value foo will be used when it’s “present”; but when it’s null or undefined, calculate bar() in its place.

Again, the above code is equivalent to the following.

let x = (foo !== null && foo !== undefined) ?
    foo :
    bar();

The ?? operator can replace uses of || when trying to use a default value. For example, the following code snippet tries to fetch the volume that was last saved in localStorage (if it ever was); however, it has a bug because it uses ||.

function initializeAudio() {
    let volume = localStorage.volume || 0.5

    // ...
}

When localStorage.volume is set to 0, the page will set the volume to 0.5 which is unintended. ?? avoids some unintended behavior from 0, NaN and "" being treated as falsy values.

We owe a large thanks to community members Wenlu Wang and Titian Cernicova Dragomir for implementing this feature! For more details, check out their pull request and the nullish coalescing proposal repository.

Assertion Functions

There’s a specific set of functions that throw an error if something unexpected happened. They’re called “assertion” functions. As an example, Node.js has a dedicated function for this called assert.

assert(someValue === 42);

In this example if someValue isn’t equal to 42, then assert will throw an AssertionError.

Assertions in JavaScript are often used to guard against improper types being passed in. For example,

function multiply(x, y) {
    assert(typeof x === "number");
    assert(typeof y === "number");

    return x * y;
}

Unfortunately in TypeScript these checks could never be properly encoded. For loosely-typed code this meant TypeScript was checking less, and for slightly conservative code it often forced users to use type assertions..

function yell(str) {
    assert(typeof str === "string");

    return str.toUppercase();
    // Oops! We misspelled 'toUpperCase'.
    // Would be great if TypeScript still caught this!
}

The alternative was to instead rewrite the code so that the language could analyze it, but this isn’t convenient.

function yell(str) {
    if (typeof str !== "string") {
        throw new TypeError("str should have been a string.")
    }
    // Error caught!
    return str.toUppercase();
}

Ultimately the goal of TypeScript is to type existing JavaScript constructs in the least disruptive way. For that reason, TypeScript 3.7 introduces a new concept called “assertion signatures” which model these assertion functions.

The first type of assertion signature models the way that Node’s assert function works. It ensures that whatever condition is being checked must be true for the remainder of the containing scope.

function assert(condition: any, msg?: string): asserts condition {
    if (!condition) {
        throw new AssertionError(msg)
    }
}

asserts condition says that whatever gets passed into the condition parameter must be true if the assert returns (because otherwise it would throw an error). That means that for the rest of the scope, that condition must be truthy. As an example, using this assertion function means we do catch our original yell example.

function yell(str) {
    assert(typeof str === "string");

    return str.toUppercase();
    //         ~~~~~~~~~~~
    // error: Property 'toUppercase' does not exist on type 'string'.
    //        Did you mean 'toUpperCase'?
}

function assert(condition: any, msg?: string): asserts condition {
    if (!condition) {
        throw new AssertionError(msg)
    }
}

The other type of assertion signature doesn’t check for a condition, but instead tells TypeScript that a specific variable or property has a different type.

function assertIsString(val: any): asserts val is string {
    if (typeof val !== "string") {
        throw new AssertionError("Not a string!");
    }
}

Here asserts val is string ensures that after any call to assertIsString, any variable passed in will be known to be a string.

function yell(str: any) {
    assertIsString(str);

    // Now TypeScript knows that 'str' is a 'string'.

    return str.toUppercase();
    //         ~~~~~~~~~~~
    // error: Property 'toUppercase' does not exist on type 'string'.
    //        Did you mean 'toUpperCase'?
}

These assertion signatures are very similar to writing type predicate signatures:

function isString(val: any): val is string {
    return typeof val === "string";
}

function yell(str: any) {
    if (isString(str)) {
        return str.toUppercase();
    }
    throw "Oops!";
}

And just like type predicate signatures, these assertion signatures are incredibly expressive. We can express some fairly sophisticated ideas with these.

function assertIsDefined<T>(val: T): asserts val is NonNullable<T> {
    if (val === undefined || val === null) {
        throw new AssertionError(
            `Expected 'val' to be defined, but received ${val}`
        );
    }
}

To read up more about assertion signatures, check out the original pull request.

Better Support for never-Returning Functions

As part of the work for assertion signatures, TypeScript needed to encode more about where and which functions were being called. This gave us the opportunity to expand support for another class of functions: functions that return never.

The intent of any function that returns never is that it never returns. It indicates that an exception was thrown, a halting error condition occurred, or that the program exited. For example, process.exit(...) in @types/node is specified to return never.

In order to ensure that a function never potentially returned undefined or effectively returned from all code paths, TypeScript needed some syntactic signal – either a return or throw at the end of a function. So users found themselves return-ing their failure functions.

function dispatch(x: string | number): SomeType {
    if (typeof x === "string") {
        return doThingWithString(x);
    }
    else if (typeof x === "number") {
        return doThingWithNumber(x);
    }
    return process.exit(1);
}

Now when these never-returning functions are called, TypeScript recognizes that they affect the control flow graph and accounts for them.

function dispatch(x: string | number): SomeType {
    if (typeof x === "string") {
        return doThingWithString(x);
    }
    else if (typeof x === "number") {
        return doThingWithNumber(x);
    }
    process.exit(1);
}

As with assertion functions, you can read up more at the same pull request.

(More) Recursive Type Aliases

Type aliases have always had a limitation in how they could be “recursively” referenced. The reason is that any use of a type alias needs to be able to replace substitute itself with whatever it aliases. In some cases, that’s not not possible, so the compiler rejects certain recursive aliases like the following:

type Foo = Foo;

This is a reasonable restriction because any use of Foo would need to be replaced with Foo which would need to be replaced with Foo which would need to be replaced with Foo which… well, hopefully you get the idea! In the end, there isn’t a type that makes sense in place of Foo.

This is fairly consistent with how other languages treat type aliases, but it does give rise to some slightly surprising scenarios for how users leverage the feature. For example, in TypeScript 3.6 and prior, the following causes an error.

type ValueOrArray<T> = T | Array<ValueOrArray<T>>;
//   ~~~~~~~~~~~~
// error: Type alias 'ValueOrArray' circularly references itself.

This is strange because there is technically nothing wrong with any use users could always write what was effectively the same code by introducing an interface.

type ValueOrArray<T> = T | ArrayOfValueOrArray<T>;

interface ArrayOfValueOrArray<T> extends Array<ValueOrArray<T>> {}

Because interfaces (and other object types) introduce a level of indirection and their full structure doesn’t need to be eagerly built out, TypeScript has no problem working with this structure.

But workaround of introducing the interface wasn’t intuitive for users. And in principle there really wasn’t anything wrong with the original version of ValueOrArray that used Array directly. If the compiler was a little bit “lazier” and only calculated the type arguments to Array when necessary, then TypeScript could express these correctly.

That’s exactly what TypeScript 3.7 introduces. At the “top level” of a type alias, TypeScript will defer resolving type arguments to permit these patterns.

This means that code like the following that was trying to represent JSON…

type Json =
    | string
    | number
    | boolean
    | null
    | JsonObject
    | JsonArray;

interface JsonObject {
    [property: string]: Json;
}

interface JsonArray extends Array<Json> {}

can finally be rewritten without helper interfaces.

type Json =
    | string
    | number
    | boolean
    | null
    | { [property: string]: Json }
    | Json[];

This new relaxation also lets us recursively reference type aliases in tuples as well. The following code which used to error is now valid TypeScript code.

type VirtualNode =
    | string
    | [string, { [key: string]: any }, ...VirtualNode[]];

const myNode: VirtualNode =
    ["div", { id: "parent" },
        ["div", { id: "first-child" }, "I'm the first child"],
        ["div", { id: "second-child" }, "I'm the second child"]
    ];

For more information, you can read up on the original pull request.

--declaration and --allowJs

The --declaration flag in TypeScript allows us to generate .d.ts files (declaration files) from source TypeScript files like .ts and .tsx files. These .d.ts files are important because they allow TypeScript to type-check against other projects without re-checking/building the original source code. For the same reason, this setting is required when using project references.

Unfortunately, --declaration didn’t work with settings like --allowJs to allow mixing TypeScript and JavaScript input files. This was a frustrating limitation because it meant users couldn’t use --declaration when migrating codebases, even if they were JSDoc-annotated. TypeScript 3.7 changes that, and allows the two features to be mixed!

When using allowJs, TypeScript will use its best-effort understanding of JavaScript source code and save that to a .d.ts file in an equivalent representation. That includes all of its JSDoc smarts, so code like the following:

/**
 * @callback Job
 * @returns {void}
 */

/** Queues work */
export class Worker {
    constructor(maxDepth = 10) {
        this.started = false;
        this.depthLimit = maxDepth;
        /**
         * NOTE: queued jobs may add more items to queue
         * @type {Job[]}
         */
        this.queue = [];
    }
    /**
     * Adds a work item to the queue
     * @param {Job} work 
     */
    push(work) {
        if (this.queue.length + 1 > this.depthLimit) throw new Error("Queue full!");
        this.queue.push(work);
    }
    /**
     * Starts the queue if it has not yet started
     */
    start() {
        if (this.started) return false;
        this.started = true;
        while (this.queue.length) {
            /** @type {Job} */(this.queue.shift())();
        }
        return true;
    }
}

will currently be transformed into the following implementation-less .d.ts file:

/**
 * @callback Job
 * @returns {void}
 */
/** Queues work */
export class Worker {
    constructor(maxDepth?: number);
    started: boolean;
    depthLimit: number;
    /**
     * NOTE: queued jobs may add more items to queue
     * @type {Job[]}
     */
    queue: Job[];
    /**
     * Adds a work item to the queue
     * @param {Job} work
     */
    push(work: Job): void;
    /**
     * Starts the queue if it has not yet started
     */
    start(): boolean;
}
export type Job = () => void;

For more details, you can check out the original pull request.

Build-Free Editing with Project References

TypeScript’s project references provide us with an easy way to break codebases up to give us faster compiles. Unfortunately, editing a project whose dependencies hadn’t been built (or whose output was out of date) meant that the editing experience wouldn’t work well.

In TypeScript 3.7, when opening a project with dependencies, TypeScript will automatically use the source .ts/.tsx files instead. This means projects using project references will now see an improved editing experience where semantic operations are up-to-date and “just work”. You can disable this behavior with the compiler option disableSourceOfProjectReferenceRedirect which may be appropriate when working in very large projects where this change may impact editing performance.

You can read up more about this change by reading up on its pull request.

Uncalled Function Checks

A common and dangerous error is to forget to invoke a function, especially if the function has zero arguments or is named in a way that implies it might be a property rather than a function.

interface User {
    isAdministrator(): boolean;
    notify(): void;
    doNotDisturb?(): boolean;
}

// later...

// Broken code, do not use!
function doAdminThing(user: User) {
    // oops!
    if (user.isAdministrator) {
        sudo();
        editTheConfiguration();
    }
    else {
        throw new AccessDeniedError("User is not an admin");
    }
}

Here, we forgot to call isAdministrator, and the code incorrectly allows non-adminstrator users to edit the configuration!

In TypeScript 3.7, this is identified as a likely error:

function doAdminThing(user: User) {
    if (user.isAdministrator) {
    //  ~~~~~~~~~~~~~~~~~~~~
    // error! This condition will always return true since the function is always defined.
    //        Did you mean to call it instead?t

This check is a breaking change, but for that reason the checks are very conservative. This error is only issued in if conditions, and it is not issued on optional properties, if strictNullChecks is off, or if the function is later called within the body of the if:

interface User {
    isAdministrator(): boolean;
    notify(): void;
    doNotDisturb?(): boolean;
}

function issueNotification(user: User) {
    if (user.doNotDisturb) {
        // OK, property is optional
    }
    if (user.notify) {
        // OK, called the function
        user.notify();
    }
}

If you intended to test the function without calling it, you can correct the definition of it to include undefined/null, or use !! to write something like if (!!user.isAdministrator) to indicate that the coercion is intentional.

We owe a big thanks to GitHub user @jwbay who took the initiative to create a proof-of-concept and iterated to provide us with with the current version.

// @ts-nocheck in TypeScript Files

TypeScript 3.7 allows us to add // @ts-nocheck comments to the top of TypeScript files to disable semantic checks. Historically this comment was only respected in JavaScript source files in the presence of checkJs, but we’ve expanded support to TypeScript files to make migrations easier for all users.

Semicolon Formatter Option

TypeScript’s built-in formatter now supports semicolon insertion and removal at locations where a trailing semicolon is optional due to JavaScript’s automatic semicolon insertion (ASI) rules. The setting is available now in Visual Studio Code Insiders, and will be available in Visual Studio 16.4 Preview 2 in the Tools Options menu.

Choosing a value of “insert” or “remove” also affects the format of auto-imports, extracted types, and other generated code provided by TypeScript services. Leaving the setting on its default value of “ignore” makes generated code match the semicolon preference detected in the current file.

Breaking Changes

DOM Changes

Types in lib.dom.d.ts have been updated. These changes are largely correctness changes related to nullability, but impact will ultimately depend on your codebase.

Function Truthy Checks

As mentioned above, TypeScript now errors when functions appear to be uncalled within if statement conditions. An error is issued when a function type is checked in if conditions unless any of the following apply:

  • the checked value comes from an optional property
  • strictNullChecks is disabled
  • the function is later called within the body of the if

Local and Imported Type Declarations Now Conflict

Due to a bug, the following construct was previously allowed in TypeScript:

// ./someOtherModule.ts
interface SomeType {
    y: string;
}

// ./myModule.ts
import { SomeType } from "./someOtherModule";
export interface SomeType {
    x: number;
}

function fn(arg: SomeType) {
    console.log(arg.x); // Error! 'x' doesn't exist on 'SomeType'
}

Here, SomeType appears to originate in both the import declaration and the local interface declaration. Perhaps surprisingly, inside the module, SomeType refers exclusively to the imported definition, and the local declaration SomeType is only usable when imported from another file. This is very confusing and our review of the very small number of cases of code like this in the wild showed that developers usually thought something different was happening.

In TypeScript 3.7, this is now correctly identified as a duplicate identifier error. The correct fix depends on the original intent of the author and should be addressed on a case-by-case basis. Usually, the naming conflict is unintentional and the best fix is to rename the imported type. If the intent was to augment the imported type, a proper module augmentation should be written instead.

What’s Next?

The final release of TypeScript 3.7 will be released near the start of November, with a release candidate available a few weeks earlier. We hope you give the beta a shot and let us know how things work. If you have any suggestions or run into any problems, don’t be afraid to drop by the issue tracker and open up an issue!

Happy Hacking!

– Daniel Rosenwasser and the TypeScript Team

Announcing TypeScript 3.3

TypeScript is a superset of JavaScript that compiles to clean JavaScript output. ... TypeScript 3.3


Today we’re happy to announce the availability of TypeScript 3.3!

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 TypeScript, you can get it through NuGet, or through npm with the following command:

npm install -g typescript

You can also get editor support by

TypeScript 3.3 is a smaller release than usual and contains no breaking changes, so it should be easy to upgrade if you’re on an older version. Let’s explore what’s new in 3.3!


Improved behavior for calling union types

When TypeScript has a union type A | B, it allows you to access all the properties common to both A and B (i.e. the intersection of members).

interface A {
    aProp: string;
    commonProp: string;
}

interface B {
bProp: number;
commonProp: number
}

type Union = A | B;

declare let x: Union;

x.aProp; // error - 'B' doesn't have the property 'aProp'
x.bProp; // error - 'A' doesn't have the property 'bProp'
x.commonProp; // okay! Both 'A' and 'B' have a property named commonProp.

This behavior should feel intuitive – you can only get a property off of a union type if it’s known to be in every type of the union.

What about, instead of accessing properties, we’re dealing with calling types? Well, when every type has exactly one signature with identical parameters, things just work and you can call these types.

type CallableA = (x: boolean) => string;
type CallableB = (x: boolean) => number;

type CallableUnion = CallableA | CallableB;

declare let f: CallableUnion;

let x = f(true); // Okay! Returns a 'string | number'.

However, this restriction was sometimes, well, overly restrictive.

type Fruit = "apple" | "orange";
type Color = "red" | "orange";

type FruitEater = (fruit: Fruit) => number; // eats and ranks the fruit
type ColorConsumer = (color: Color) => string; // consumes and describes the colors

declare let f: FruitEater | ColorConsumer;

// Cannot invoke an expression whose type lacks a call signature.
// Type 'FruitEater | ColorConsumer' has no compatible call signatures.ts(2349)
f("orange");

Silly example and poor error message aside, both FruitEaters and ColorConsumers should be able to take the string "orange", and return either a number or a string.

In TypeScript 3.3, this is no longer an error.

type Fruit = "apple" | "orange";
type Color = "red" | "orange";

type FruitEater = (fruit: Fruit) => number; // eats and ranks the fruit
type ColorConsumer = (color: Color) => string; // consumes and describes the colors

declare let f: FruitEater | ColorConsumer;

f("orange"); // It works! Returns a 'number | string'.

f("apple"); // error - Argument of type '"apple"' is not assignable to parameter of type '"orange"'.

f("red"); // error - Argument of type '"red"' is not assignable to parameter of type '"orange"'.

In TypeScript 3.3, the parameters of these signatures are intersected together to create a new signature. In the example above, the parameters fruit and color are intersected together to a new parameter of type Fruit & Color. Fruit & Color is really the same as ("apple" | "orange") & ("red" | "orange") which is equivalent to ("apple" & "red") | ("apple" & "orange") | ("orange" & "red") | ("orange" & "orange"). Each of those impossible intersections evaporates, and we’re left with "orange" & "orange" which is just "orange".

There are still some restrictions though. This new behavior only kicks in when at most one type in the union has multiple overloads, and at most one type in the union has a generic signature. That means methods on number[] | string[] like map (which is generic) still won’t be callable.

On the other hand, methods like forEach will now be callable, but under noImplicitAny there may be some issues.

interface Dog {
kind: "pupper"
dogProp: any;
}
interface Cat {
kind: "kittyface"
catProp: any;
}

const catOrDogArray: Dog[] | Cat[] = [];

catOrDogArray.forEach(animal => {
// ~~~~~~ error!
// Parameter 'animal' implicitly has an 'any' type.
});

While we’ll continue to improve the experience here, this is strictly more capable in TypeScript 3.3, and adding an explicit type annotation will work.

interface Dog {
kind: "pupper"
dogProp: any;
}
interface Cat {
kind: "kittyface"
catProp: any;
}

const catOrDogArray: Dog[] | Cat[] = [];
catOrDogArray.forEach((animal: Dog | Cat) => {
if (animal.kind === "pupper") {
animal.dogProp;
// ...
}
else if (animal.kind === "kittyface") {
animal.catProp;
// ...
}
});

Incremental file watching for composite projects in --build --watch

In TypeScript 3.0, we introduced a new feature for structuring builds called “composite projects”. Part of the goal here was to ensure users could break up large projects into smaller parts that build quickly and preserve project structure, without compromising the existing TypeScript experience. Thanks to composite projects, TypeScript can use --build mode to recompile only the set of projects and dependencies. You can think of this as optimizing inter-project builds.

However, around last year our team also shipped optimized --watch mode builds via a new incremental “builder” API. In a similar vein, the entire idea is that this mode only re-checks and re-emits changed files or files whose dependencies might impact type-checking. You can think of this as optimizing intra-project builds.

Perhaps ironically, building composite projects using --build --watch actually didn’t use this infrastructure. An update in one project under --build --watch mode would force a full build of that project, rather than determining which files within that project were affected.

In TypeScript 3.3, --build mode’s --watch flag does leverage incremental file watching as well. That can mean signficantly faster builds under --build --watch. In our testing, this functionality has resulted in a reduction of 50% to 75% in build times of the original --build --watch times. You can read more on the original pull request for the change to see specific numbers, but we believe most composite project users will see significant wins here.


JavaScript editing in Sublime Text

Thanks to work done by former TypeScript team member Zhengbo Li and community contributor @idiotWu, our TypeScript plugin for Sublime Text now supports editing in JavaScript files! This means users will get more accurate completions, rename, go-to-definition, and more in JavaScript code that utilizes JSDoc and interoperates with TypeScript code.


What’s next?

In case you missed it, the TypeScript project recently published our 6-month roadmap to outline the work we expect to tackle between January and June. To keep track of upcoming features in TypeScript 3.4 and forward, you can keep an eye on our feature roadmap page.

We hope that TypeScript 3.3 continues to make you more productive, and makes you happier as you code. If you’re enjoying it, let us know on Twitter, and if you’ve got any suggestions on what we can improve, file an issue on GitHub.

Happy hacking!

– Daniel Rosenwasser and the TypeScript team


Learn More

Understanding TypeScript

Learn TypeScript From Scratch

TypeScript For Absolute Beginners - Make Typescript App

Angular 6 (formerly Angular 2) - The Complete Guide

The Complete JavaScript Course 2018: Build Real Projects!

The Web Developer Bootcamp

TypeScript 3.7 is finally here!

TypeScript 3.7 is finally here!

We’re thrilled to announce the release of TypeScript 3.7, a release packed with awesome new language, compiler, and tooling features: ✔ Optional Chaining & Coalescing, ✔ Assertion Functions, ✔ .d.ts Emit From .js Files, ✔ Smarter Control Flow Analysis, ✔ Flatter Error Messages

We’re thrilled to announce the release of TypeScript 3.7, a release packed with awesome new language, compiler, and tooling features.

If you haven’t yet heard of TypeScript, it’s a language based on JavaScript that adds static type-checking along with type syntax. Static type-checking lets us know about problems with our code before we try to run it by reporting errors if we do something questionable. This ranges from type coercions that can happen in code like 42 / "hello", or even basic typos on property names. But beyond this, TypeScript powers things like completions, quick fixes, and refactorings for both TypeScript and JavaScript in some of your favorite editors. In fact, if you already use Visual Studio or Visual Studio Code, you might already be using TypeScript when you write JavaScript code! So if you’re interested in learning more, head over to our website.

If you’re already ready to use TypeScript, you can get it through NuGet, or use npm with the following command:

npm install typescript

You can also get editor support by

We’ve got a lot of great features in TypeScript 3.7, including:

This is a pretty extensive list! If you’re into reading, you’re in for some fun with this release. But if you’re the type of person who likes to learn by getting their hands dirty, check out the TypeScript playground where we’ve added an entire menu for learning what’s new.

Without further ado, let’s dive in and look at what’s new!

Optional Chaining

TypeScript 3.7 implements one of the most highly-demanded ECMAScript features yet: optional chaining!

Optional chaining is issue #16 on our issue tracker. For context, there have been over 23,000 issues filed on the TypeScript issue tracker to date. This one was filed over 5 years ago – before there was even a formal proposal within TC39. For years, we’ve been asked to implement the feature, but our stance has long been not to conflict with potential ECMAScript proposals. Instead, our team recently took the steps to help drive the proposal to standardization, and ultimately to all JavaScript and TypeScript users. In fact, we became involved to the point where we were championing the proposal! With its advancement to stage 3, we’re comfortable and proud to release it as part of TypeScript 3.7.

So what is optional chaining? Well at its core, optional chaining lets us write code where we can immediately stop running some expressions if we run into a null or undefined. The star of the show in optional chaining is the new ?. operator for optional property accesses. When we write code like

let x = foo?.bar.baz();

this is a way of saying that when foo is defined, foo.bar.baz() will be computed; but when foo is null or undefined, stop what we’re doing and just return undefined.

More plainly, that code snippet is the same as writing the following.

let x = (foo === null || foo === undefined) ?
    undefined :
    foo.bar.baz();

Note that if bar is null or undefined, our code will still hit an error accessing baz. Likewise, if baz is null or undefined, we’ll hit an error at the call site. ?. only checks for whether the value on the left of it is null or undefined – not any of the subsequent properties.

You might find yourself using ?. to replace a lot of code that performs repetitive nullish checks using the && operator.

// Before
if (foo && foo.bar && foo.bar.baz) {
    // ...
}

// After-ish
if (foo?.bar?.baz) {
    // ...
}

Keep in mind that ?. acts differently than those && operations since && will act specially on “falsy” values (e.g. the empty string, 0, NaN, and, well, false), but this is an intentional feature of the construct. It doesn’t short-circuit on valid data like 0 or empty strings.

Optional chaining also includes two other operations. First there’s the optional element access which acts similarly to optional property accesses, but allows us to access non-identifier properties (e.g. arbitrary strings, numbers, and symbols):

/**
 * Get the first element of the array if we have an array.
 * Otherwise return undefined.
 */
function tryGetFirstElement<T>(arr?: T[]) {
    return arr?.[0];
    // equivalent to
    //   return (arr === null || arr === undefined) ?
    //       undefined :
    //       arr[0];
}

There’s also optional call, which allows us to conditionally call expressions if they’re not null or undefined.

async function makeRequest(url: string, log?: (msg: string) => void) {
    log?.(`Request started at ${new Date().toISOString()}`);
    // roughly equivalent to
    //   if (log != null) {
    //       log(`Request started at ${new Date().toISOString()}`);
    //   }

    const result = (await fetch(url)).json();

    log?.(`Request finished at at ${new Date().toISOString()}`);

    return result;
}

The “short-circuiting” behavior that optional chains have is limited property accesses, calls, element accesses – it doesn’t expand any further out from these expressions. In other words,

let result = foo?.bar / someComputation()

doesn’t stop the division or someComputation() call from occurring. It’s equivalent to

let temp = (foo === null || foo === undefined) ?
    undefined :
    foo.bar;

let result = temp / someComputation();

That might result in dividing undefined, which is why in strictNullChecks, the following is an error.

function barPercentage(foo?: { bar: number }) {
    return foo?.bar / 100;
    //     ~~~~~~~~
    // Error: Object is possibly undefined.
}

More more details, you can read up on the proposal and view the original pull request.

Nullish Coalescing

The nullish coalescing operator is another upcoming ECMAScript feature that goes hand-in-hand with optional chaining, and which our team has been involved with championing.

You can think of this feature – the ?? operator – as a way to “fall back” to a default value when dealing with null or undefined. When we write code like

let x = foo ?? bar();

this is a new way to say that the value foo will be used when it’s “present”; but when it’s null or undefined, calculate bar() in its place.

Again, the above code is equivalent to the following.

let x = (foo !== null && foo !== undefined) ?
    foo :
    bar();

The ?? operator can replace uses of || when trying to use a default value. For example, the following code snippet tries to fetch the volume that was last saved in localStorage (if it ever was); however, it has a bug because it uses ||.

function initializeAudio() {
    let volume = localStorage.volume || 0.5

    // ...
}

When localStorage.volume is set to 0, the page will set the volume to 0.5 which is unintended. ?? avoids some unintended behavior from 0, NaN and "" being treated as falsy values.

We owe a large thanks to community members Wenlu Wang and Titian Cernicova Dragomir for implementing this feature! For more details, check out their pull request and the nullish coalescing proposal repository.

Assertion Functions

There’s a specific set of functions that throw an error if something unexpected happened. They’re called “assertion” functions. As an example, Node.js has a dedicated function for this called assert.

assert(someValue === 42);

In this example if someValue isn’t equal to 42, then assert will throw an AssertionError.

Assertions in JavaScript are often used to guard against improper types being passed in. For example,

function multiply(x, y) {
    assert(typeof x === "number");
    assert(typeof y === "number");

    return x * y;
}

Unfortunately in TypeScript these checks could never be properly encoded. For loosely-typed code this meant TypeScript was checking less, and for slightly conservative code it often forced users to use type assertions..

function yell(str) {
    assert(typeof str === "string");

    return str.toUppercase();
    // Oops! We misspelled 'toUpperCase'.
    // Would be great if TypeScript still caught this!
}

The alternative was to instead rewrite the code so that the language could analyze it, but this isn’t convenient.

function yell(str) {
    if (typeof str !== "string") {
        throw new TypeError("str should have been a string.")
    }
    // Error caught!
    return str.toUppercase();
}

Ultimately the goal of TypeScript is to type existing JavaScript constructs in the least disruptive way. For that reason, TypeScript 3.7 introduces a new concept called “assertion signatures” which model these assertion functions.

The first type of assertion signature models the way that Node’s assert function works. It ensures that whatever condition is being checked must be true for the remainder of the containing scope.

function assert(condition: any, msg?: string): asserts condition {
    if (!condition) {
        throw new AssertionError(msg)
    }
}

asserts condition says that whatever gets passed into the condition parameter must be true if the assert returns (because otherwise it would throw an error). That means that for the rest of the scope, that condition must be truthy. As an example, using this assertion function means we do catch our original yell example.

function yell(str) {
    assert(typeof str === "string");

    return str.toUppercase();
    //         ~~~~~~~~~~~
    // error: Property 'toUppercase' does not exist on type 'string'.
    //        Did you mean 'toUpperCase'?
}

function assert(condition: any, msg?: string): asserts condition {
    if (!condition) {
        throw new AssertionError(msg)
    }
}

The other type of assertion signature doesn’t check for a condition, but instead tells TypeScript that a specific variable or property has a different type.

function assertIsString(val: any): asserts val is string {
    if (typeof val !== "string") {
        throw new AssertionError("Not a string!");
    }
}

Here asserts val is string ensures that after any call to assertIsString, any variable passed in will be known to be a string.

function yell(str: any) {
    assertIsString(str);

    // Now TypeScript knows that 'str' is a 'string'.

    return str.toUppercase();
    //         ~~~~~~~~~~~
    // error: Property 'toUppercase' does not exist on type 'string'.
    //        Did you mean 'toUpperCase'?
}

These assertion signatures are very similar to writing type predicate signatures:

function isString(val: any): val is string {
    return typeof val === "string";
}

function yell(str: any) {
    if (isString(str)) {
        return str.toUppercase();
    }
    throw "Oops!";
}

And just like type predicate signatures, these assertion signatures are incredibly expressive. We can express some fairly sophisticated ideas with these.

function assertIsDefined<T>(val: T): asserts val is NonNullable<T> {
    if (val === undefined || val === null) {
        throw new AssertionError(
            `Expected 'val' to be defined, but received ${val}`
        );
    }
}

To read up more about assertion signatures, check out the original pull request.

Better Support for never-Returning Functions

As part of the work for assertion signatures, TypeScript needed to encode more about where and which functions were being called. This gave us the opportunity to expand support for another class of functions: functions that return never.

The intent of any function that returns never is that it never returns. It indicates that an exception was thrown, a halting error condition occurred, or that the program exited. For example, process.exit(...) in @types/node is specified to return never.

In order to ensure that a function never potentially returned undefined or effectively returned from all code paths, TypeScript needed some syntactic signal – either a return or throw at the end of a function. So users found themselves return-ing their failure functions.

function dispatch(x: string | number): SomeType {
    if (typeof x === "string") {
        return doThingWithString(x);
    }
    else if (typeof x === "number") {
        return doThingWithNumber(x);
    }
    return process.exit(1);
}

Now when these never-returning functions are called, TypeScript recognizes that they affect the control flow graph and accounts for them.

function dispatch(x: string | number): SomeType {
    if (typeof x === "string") {
        return doThingWithString(x);
    }
    else if (typeof x === "number") {
        return doThingWithNumber(x);
    }
    process.exit(1);
}

As with assertion functions, you can read up more at the same pull request.

--declaration and --allowJs

The --declaration flag in TypeScript allows us to generate .d.ts files (declaration files) from TypeScript source files (i.e. .ts and .tsx files). These .d.ts files are important for a couple of reasons.

First of all, they’re important because they allow TypeScript to type-check against other projects without re-checking the original source code. They’re also important because they allow TypeScript to interoperate with existing JavaScript libraries that weren’t built with TypeScript in mind. Finally, a benefit that is often underappreciated: both TypeScript and JavaScript users can benefit from these files when using editors powered by TypeScript to get things like better auto-completion.

Unfortunately, --declaration didn’t work with the --allowJs flag which allows mixing TypeScript and JavaScript input files. This was a frustrating limitation because it meant users couldn’t use the --declaration flag when migrating codebases, even if they were JSDoc-annotated. TypeScript 3.7 changes that, and allows the two options to be used together!

The most impactful outcome of this feature might a bit subtle: with TypeScript 3.7, users can write libraries in JSDoc annotated JavaScript and support TypeScript users.

The way that this works is that when using allowJs, TypeScript has some best-effort analyses to understand common JavaScript patterns; however, the way that some patterns are expressed in JavaScript don’t necessarily look like their equivalents in TypeScript. When declaration emit is turned on, TypeScript figures out the best way to transform JSDoc comments and CommonJS exports into valid type declarations and the like in the output .d.ts files.

As an example, the following code snippet

const assert = require("assert")

module.exports.blurImage = blurImage;

/**
 * Produces a blurred image from an input buffer.
 * 
 * @param input {Uint8Array}
 * @param width {number}
 * @param height {number}
 */
function blurImage(input, width, height) {
    const numPixels = width * height * 4;
    assert(input.length === numPixels);
    const result = new Uint8Array(numPixels);

    // TODO

    return result;
}

Will produce a .d.ts file like

/**
 * Produces a blurred image from an input buffer.
 *
 * @param input {Uint8Array}
 * @param width {number}
 * @param height {number}
 */
export function blurImage(input: Uint8Array, width: number, height: number): Uint8Array;

This can go beyond basic functions with @param tags too, where the following example:

/**
 * @callback Job
 * @returns {void}
 */

/** Queues work */
export class Worker {
    constructor(maxDepth = 10) {
        this.started = false;
        this.depthLimit = maxDepth;
        /**
         * NOTE: queued jobs may add more items to queue
         * @type {Job[]}
         */
        this.queue = [];
    }
    /**
     * Adds a work item to the queue
     * @param {Job} work 
     */
    push(work) {
        if (this.queue.length + 1 > this.depthLimit) throw new Error("Queue full!");
        this.queue.push(work);
    }
    /**
     * Starts the queue if it has not yet started
     */
    start() {
        if (this.started) return false;
        this.started = true;
        while (this.queue.length) {
            /** @type {Job} */(this.queue.shift())();
        }
        return true;
    }
}

will be transformed into the following .d.ts file:

/**
 * @callback Job
 * @returns {void}
 */
/** Queues work */
export class Worker {
    constructor(maxDepth?: number);
    started: boolean;
    depthLimit: number;
    /**
     * NOTE: queued jobs may add more items to queue
     * @type {Job[]}
     */
    queue: Job[];
    /**
     * Adds a work item to the queue
     * @param {Job} work
     */
    push(work: Job): void;
    /**
     * Starts the queue if it has not yet started
     */
    start(): boolean;
}
export type Job = () => void;

Note that when using these flags together, TypeScript doesn’t necessarily have to downlevel .js files. If you simply want TypeScript to create .d.ts files, you can use the --emitDeclarationOnly compiler option.

For more details, you can check out the original pull request.

(More) Recursive Type Aliases

Type aliases have always had a limitation in how they could be “recursively” referenced. The reason is that any use of a type alias needs to be able to substitute itself with whatever it aliases. In some cases, that’s not possible, so the compiler rejects certain recursive aliases like the following:

type Foo = Foo;

This is a reasonable restriction because any use of Foo would need to be replaced with Foo which would need to be replaced with Foo which would need to be replaced with Foo which… well, hopefully you get the idea! In the end, there isn’t a type that makes sense in place of Foo.

This is fairly consistent with how other languages treat type aliases, but it does give rise to some slightly surprising scenarios for how users leverage the feature. For example, in TypeScript 3.6 and prior, the following causes an error.

type ValueOrArray<T> = T | Array<ValueOrArray<T>>;
//   ~~~~~~~~~~~~
// error: Type alias 'ValueOrArray' circularly references itself.

This is strange because there is technically nothing wrong with any use users could always write what was effectively the same code by introducing an interface.

type ValueOrArray<T> = T | ArrayOfValueOrArray<T>;

interface ArrayOfValueOrArray<T> extends Array<ValueOrArray<T>> {}

Because interfaces (and other object types) introduce a level of indirection and their full structure doesn’t need to be eagerly built out, TypeScript has no problem working with this structure.

But the workaround of introducing the interface wasn’t intuitive for users. And in principle there really wasn’t anything wrong with the original version of ValueOrArray that used Array directly. If the compiler was a little bit “lazier” and only calculated the type arguments to Array when necessary, then TypeScript could express these correctly.

That’s exactly what TypeScript 3.7 introduces. At the “top level” of a type alias, TypeScript will defer resolving type arguments to permit these patterns.

This means that code like the following that was trying to represent JSON…

type Json =
    | string
    | number
    | boolean
    | null
    | JsonObject
    | JsonArray;

interface JsonObject {
    [property: string]: Json;
}

interface JsonArray extends Array<Json> {}

can finally be rewritten without helper interfaces.

type Json =
    | string
    | number
    | boolean
    | null
    | { [property: string]: Json }
    | Json[];

This new relaxation also lets us recursively reference type aliases in tuples as well. The following code which used to error is now valid TypeScript code.

type VirtualNode =
    | string
    | [string, { [key: string]: any }, ...VirtualNode[]];

const myNode: VirtualNode =
    ["div", { id: "parent" },
        ["div", { id: "first-child" }, "I'm the first child"],
        ["div", { id: "second-child" }, "I'm the second child"]
    ];

For more information, you can read up on the original pull request.

The useDefineForClassFields Flag and The declare Property Modifier

Back when TypeScript implemented public class fields, we assumed to the best of our abilities that the following code

class C {
    foo = 100;
    bar: string;
}

would be equivalent to a similar assignment within a constructor body.

class C {
    constructor() {
        this.foo = 100;
    }
}

Unfortunately, while this seemed to be the direction that the proposal moved towards in its earlier days, there is an extremely strong chance that public class fields will be standardized differently. Instead, the original code sample might need to de-sugar to something closer to the following:

class C {
    constructor() {
        Object.defineProperty(this, "foo", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 100
        });
        Object.defineProperty(this, "bar", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
    }
}

While TypeScript 3.7 isn’t changing any existing emit by default, we’ve been rolling out changes incrementally to help users mitigate potential future breakage. We’ve provided a new flag called useDefineForClassFields to enable this emit mode with some new checking logic.

The two biggest changes are the following:

  • Declarations are initialized with Object.defineProperty.
  • Declarations are always initialized to undefined, even if they have no initializer.

This can cause quite a bit of fallout for existing code that use inheritance. First of all, set accessors from base classes won’t get triggered – they’ll be completely overwritten.

class Base {
    set data(value: string) {
        console.log("data changed to " + value);
    }
}

class Derived extends Base {
    // No longer triggers a 'console.log' 
    // when using 'useDefineForClassFields'.
    data = 10;
}

Secondly, using class fields to specialize properties from base classes also won’t work.

interface Animal { animalStuff: any }
interface Dog extends Animal { dogStuff: any }

class AnimalHouse {
    resident: Animal;
    constructor(animal: Animal) {
        this.resident = animal;
    }
}

class DogHouse extends AnimalHouse {
    // Initializes 'resident' to 'undefined'
    // after the call to 'super()' when
    // using 'useDefineForClassFields'!
    resident: Dog;

    constructor(dog: Dog) {
        super(dog);
    }
}

What these two boil down to is that mixing properties with accessors is going to cause issues, and so will re-declaring properties with no initializers.

To detect the issue around accessors, TypeScript 3.7 will now emit get/set accessors in .d.ts files so that TypeScript can check for overridden accessors.

Code that’s impacted by the class fields change can get around the issue by converting field initializers to assignments in constructor bodies.

class Base {
    set data(value: string) {
        console.log("data changed to " + value);
    }
}

class Derived extends Base {
    constructor() {
        this.data = 10;
    }
}

To help mitigate the second issue, you can either add an explicit initializer or add a declare modifier to indicate that a property should have no emit.

interface Animal { animalStuff: any }
interface Dog extends Animal { dogStuff: any }

class AnimalHouse {
    resident: Animal;
    constructor(animal: Animal) {
        this.resident = animal;
    }
}

class DogHouse extends AnimalHouse {
    declare resident: Dog;
//  ^^^^^^^
// 'resident' now has a 'declare' modifier,
// and won't produce any output code.

    constructor(dog: Dog) {
        super(dog);
    }
}

Currently useDefineForClassFields is only available when targeting ES5 and upwards, since Object.defineProperty doesn’t exist in ES3. To achieve similar checking for issues, you can create a seperate project that targets ES5 and uses --noEmit to avoid a full build.

For more information, you can take a look at the original pull request for these changes.

We strongly encourage users to try the useDefineForClassFields flag and report back on our issue tracker or in the comments below. This includes feedback on difficulty of adopting the flag so we can understand how we can make migration easier.

Build-Free Editing with Project References

TypeScript’s project references provide us with an easy way to break codebases up to give us faster compiles. Unfortunately, editing a project whose dependencies hadn’t been built (or whose output was out of date) meant that the editing experience wouldn’t work well.

In TypeScript 3.7, when opening a project with dependencies, TypeScript will automatically use the source .ts/.tsx files instead. This means projects using project references will now see an improved editing experience where semantic operations are up-to-date and “just work”. You can disable this behavior with the compiler option disableSourceOfProjectReferenceRedirect which may be appropriate when working in very large projects where this change may impact editing performance.

You can read up more about this change by reading up on its pull request.

Uncalled Function Checks

A common and dangerous error is to forget to invoke a function, especially if the function has zero arguments or is named in a way that implies it might be a property rather than a function.

interface User {
    isAdministrator(): boolean;
    notify(): void;
    doNotDisturb?(): boolean;
}

// later...

// Broken code, do not use!
function doAdminThing(user: User) {
    // oops!
    if (user.isAdministrator) {
        sudo();
        editTheConfiguration();
    }
    else {
        throw new AccessDeniedError("User is not an admin");
    }
}

Here, we forgot to call isAdministrator, and the code incorrectly allows non-adminstrator users to edit the configuration!

In TypeScript 3.7, this is identified as a likely error:

function doAdminThing(user: User) {
    if (user.isAdministrator) {
    //  ~~~~~~~~~~~~~~~~~~~~
    // error! This condition will always return true since the function is always defined.
    //        Did you mean to call it instead?

This check is a breaking change, but for that reason the checks are very conservative. This error is only issued in if conditions, and it is not issued on optional properties, if strictNullChecks is off, or if the function is later called within the body of the if:

interface User {
    isAdministrator(): boolean;
    notify(): void;
    doNotDisturb?(): boolean;
}

function issueNotification(user: User) {
    if (user.doNotDisturb) {
        // OK, property is optional
    }
    if (user.notify) {
        // OK, called the function
        user.notify();
    }
}

If you intended to test the function without calling it, you can correct the definition of it to include undefined/null, or use !! to write something like if (!!user.isAdministrator) to indicate that the coercion is intentional.

We owe a big thanks to GitHub user @jwbay who took the initiative to create a proof-of-concept and iterated to provide us with with the current version.

Flatter Error Reporting

Sometimes, pretty simple code can lead to long pyramids of error messages in TypeScript. For example, this code

type SomeVeryBigType = { a: { b: { c: { d: { e: { f(): string } } } } } }
type AnotherVeryBigType = { a: { b: { c: { d: { e: { f(): number } } } } } }

declare let x: SomeVeryBigType;
declare let y: AnotherVeryBigType;

y = x;

resulted in the following error message in previous versions of TypeScript:

Type 'SomeVeryBigType' is not assignable to type 'AnotherVeryBigType'.
  Types of property 'a' are incompatible.
    Type '{ b: { c: { d: { e: { f(): string; }; }; }; }; }' is not assignable to type '{ b: { c: { d: { e: { f(): number; }; }; }; }; }'.
      Types of property 'b' are incompatible.
        Type '{ c: { d: { e: { f(): string; }; }; }; }' is not assignable to type '{ c: { d: { e: { f(): number; }; }; }; }'.
          Types of property 'c' are incompatible.
            Type '{ d: { e: { f(): string; }; }; }' is not assignable to type '{ d: { e: { f(): number; }; }; }'.
              Types of property 'd' are incompatible.
                Type '{ e: { f(): string; }; }' is not assignable to type '{ e: { f(): number; }; }'.
                  Types of property 'e' are incompatible.
                    Type '{ f(): string; }' is not assignable to type '{ f(): number; }'.
                      Types of property 'f' are incompatible.
                        Type '() => string' is not assignable to type '() => number'.
                          Type 'string' is not assignable to type 'number'.

The error message is correct, but ends up intimidating users through a wall of repetitive text. The ultimate thing we want to know is obscured by all the information about how we got to a specific type.

[We iterated on ideas])(microsoft/TypeScript/issues/33361), so now in TypeScript 3.7, errors like this are flattened to a message like the following:

Type 'SomeVeryBigType' is not assignable to type 'AnotherVeryBigType'.
  The types returned by 'a.b.c.d.e.f()' are incompatible between these types.
    Type 'string' is not assignable to type 'number'.

For more details, you can check out the original PR.

// @ts-nocheck in TypeScript Files

TypeScript 3.7 allows us to add // @ts-nocheck comments to the top of TypeScript files to disable semantic checks. Historically this comment was only respected in JavaScript source files in the presence of checkJs, but we’ve expanded support to TypeScript files to make migrations easier for all users.

Semicolon Formatter Option

TypeScript’s built-in formatter now supports semicolon insertion and removal at locations where a trailing semicolon is optional due to JavaScript’s automatic semicolon insertion (ASI) rules. The setting is available now in Visual Studio Code Insiders, and will be available in Visual Studio 16.4 Preview 2 in the Tools Options menu.

Choosing a value of “insert” or “remove” also affects the format of auto-imports, extracted types, and other generated code provided by TypeScript services. Leaving the setting on its default value of “ignore” makes generated code match the semicolon preference detected in the current file.

Website and Playground Updates

We’ll be talking more about this in the near future, but if you haven’t seen it already, you should check out the significantly upgraded TypeScript playground which now includes awesome new features like quick fixes to fix errors, dark/high-contrast mode, and automatic type acquisition so you can import other packages! On top of all of that, each feature here is explained through interactive code snippets under the “what’s new” menu.

As a cherry on top, outside of the handbook we now have search powered by Algolia on the website, allowing you to search through the handbook, release notes, and more!

Feel free to keep an eye on development of the website over here.

Breaking Changes

DOM Changes

Types in lib.dom.d.ts have been updated. These changes are largely correctness changes related to nullability, but impact will ultimately depend on your codebase.

Class Field Mitigations

As mentioned above, TypeScript 3.7 emits get/set accessors in .d.ts files which can cause breaking changes for consumers on older versions of TypeScript like 3.5 and prior. TypeScript 3.6 users will not be impacted, since that version was future-proofed for this feature.

While not a breakage per se, opting in to the useDefineForClassFields flag can cause breakage when:

  • overriding an accessor in a derived class with a property declaration
  • re-declaring a property declaration with no initializer

To understand the full impact, read the section above on the useDefineForClassFields flag.

Function Truthy Checks

As mentioned above, TypeScript now errors when functions appear to be uncalled within if statement conditions. An error is issued when a function type is checked in if conditions unless any of the following apply:

  • the checked value comes from an optional property
  • strictNullChecks is disabled
  • the function is later called within the body of the if

Local and Imported Type Declarations Now Conflict

Due to a bug, the following construct was previously allowed in TypeScript:

// ./someOtherModule.ts
interface SomeType {
    y: string;
}

// ./myModule.ts
import { SomeType } from "./someOtherModule";
export interface SomeType {
    x: number;
}

function fn(arg: SomeType) {
    console.log(arg.x); // Error! 'x' doesn't exist on 'SomeType'
}

Here, SomeType appears to originate in both the import declaration and the local interface declaration. Perhaps surprisingly, inside the module, SomeType refers exclusively to the imported definition, and the local declaration SomeType is only usable when imported from another file. This is very confusing and our review of the very small number of cases of code like this in the wild showed that developers usually thought something different was happening.

In TypeScript 3.7, this is now correctly identified as a duplicate identifier error. The correct fix depends on the original intent of the author and should be addressed on a case-by-case basis. Usually, the naming conflict is unintentional and the best fix is to rename the imported type. If the intent was to augment the imported type, a proper module augmentation should be written instead.

API Changes

To enable the recursive type alias patterns described above, the typeArguments property has been removed from the TypeReference interface. Users should instead use the getTypeArguments function on TypeChecker instances.

What’s Next?

As you enjoy TypeScript 3.7, you can take a glance at what’s coming in TypeScript 3.8! We recently posted the TypeScript 3.8 Iteration Plan, and we’ll be updating our rolling feature Roadmap as more details come together.

We want our users to truly feel joy when they write code, and we hope that TypeScript 3.7 does just that. So enjoy, and happy hacking!

The TypeScript Team