Type parameters as constraints

With TypeScript 1.8 it becomes possible for a type parameter constraint to reference type parameters from the same type parameter list. Previously this was an error. This capability is usually referred to as F-Bounded Polymorphism.

Example
  1. ts
    function assign<T extends U, U>(target: T, source: U): T {
  2. for (let id in source) {
  3. target[id] = source[id];
  4. }
  5. return target;
  6. }
  7. let x = { a: 1, b: 2, c: 3, d: 4 };
  8. assign(x, { b: 10, d: 20 });
  9. assign(x, { e: 0 }); // Error

Control flow analysis errors

TypeScript 1.8 introduces control flow analysis to help catch common errors that users tend to run into. Read on to get more details, and check out these errors in action:

cfa

Unreachable code

Statements guaranteed to not be executed at run time are now correctly flagged as unreachable code errors. For instance, statements following unconditional return, throw, break or continue statements are considered unreachable. Use --allowUnreachableCode to disable unreachable code detection and reporting.

Example

Here’s a simple example of an unreachable code error:

  1. ts
    function f(x) {
  2. if (x) {
  3. return true;
  4. } else {
  5. return false;
  6. }
  7. x = 0; // Error: Unreachable code detected.
  8. }

A more common error that this feature catches is adding a newline after a return statement:

  1. ts
    function f() {
  2. return; // Automatic Semicolon Insertion triggered at newline
  3. {
  4. x: "string"; // Error: Unreachable code detected.
  5. }
  6. }

Since JavaScript automatically terminates the return statement at the end of the line, the object literal becomes a block.

Unused labels

Unused labels are also flagged. Just like unreachable code checks, these are turned on by default; use --allowUnusedLabels to stop reporting these errors.

Example
  1. ts
    loop: while (x > 0) {
  2. // Error: Unused label.
  3. x++;
  4. }

Implicit returns

Functions with code paths that do not return a value in JS implicitly return undefined. These can now be flagged by the compiler as implicit returns. The check is turned off by default; use --noImplicitReturns to turn it on.

Example
  1. ts
    function f(x) {
  2. // Error: Not all code paths return a value.
  3. if (x) {
  4. return false;
  5. }
  6. // implicitly returns `undefined`
  7. }

Case clause fall-throughs

TypeScript can reports errors for fall-through cases in switch statement where the case clause is non-empty. This check is turned off by default, and can be enabled using --noFallthroughCasesInSwitch.

Example

With --noFallthroughCasesInSwitch, this example will trigger an error:

  1. ts
    switch (x % 2) {
  2. case 0: // Error: Fallthrough case in switch.
  3. console.log("even");
  4. case 1:
  5. console.log("odd");
  6. break;
  7. }

However, in the following example, no error will be reported because the fall-through case is empty:

  1. ts
    switch (x % 3) {
  2. case 0:
  3. case 1:
  4. console.log("Acceptable");
  5. break;
  6. case 2:
  7. console.log("This is *two much*!");
  8. break;
  9. }

Function Components in React

TypeScript now supports Function components. These are lightweight components that easily compose other components:

  1. ts
    // Use parameter destructuring and defaults for easy definition of 'props' type
  2. const Greeter = ({ name = "world" }) => <div>Hello, {name}!</div>;
  3. // Properties get validated
  4. let example = <Greeter name="TypeScript 1.8" />;

For this feature and simplified props, be sure to be use the latest version of react.d.ts.

Simplified props type management in React

In TypeScript 1.8 with the latest version of react.d.ts (see above), we’ve also greatly simplified the declaration of props types.

Specifically:

  • You no longer need to either explicitly declare ref and key or extend React.Props
  • The ref and key properties will appear with correct types on all components
  • The ref property is correctly disallowed on instances of Stateless Function components

Augmenting global/module scope from modules

Users can now declare any augmentations that they want to make, or that any other consumers already have made, to an existing module. Module augmentations look like plain old ambient module declarations (i.e. the declare module "foo" { } syntax), and are directly nested either your own modules, or in another top level ambient external module.

Furthermore, TypeScript also has the notion of global augmentations of the form declare global { }. This allows modules to augment global types such as Array if necessary.

The name of a module augmentation is resolved using the same set of rules as module specifiers in import and export declarations. The declarations in a module augmentation are merged with any existing declarations the same way they would if they were declared in the same file.

Neither module augmentations nor global augmentations can add new items to the top level scope - they can only “patch” existing declarations.

Example

Here map.ts can declare that it will internally patch the Observable type from observable.ts and add the map method to it.

  1. ts
    // observable.ts
  2. export class Observable<T> {
  3. // ...
  4. }
  1. ts
    // map.ts
  2. import { Observable } from "./observable";
  3. // Create an augmentation for "./observable"
  4. declare module "./observable" {
  5. // Augment the 'Observable' class definition with interface merging
  6. interface Observable<T> {
  7. map<U>(proj: (el: T) => U): Observable<U>;
  8. }
  9. }
  10. Observable.prototype.map = /*...*/;
  1. ts
    // consumer.ts
  2. import { Observable } from "./observable";
  3. import "./map";
  4. let o: Observable<number>;
  5. o.map(x => x.toFixed());

Similarly, the global scope can be augmented from modules using a declare global declarations:

Example
  1. ts
    // Ensure this is treated as a module.
  2. export {};
  3. declare global {
  4. interface Array<T> {
  5. mapToNumbers(): number[];
  6. }
  7. }
  8. Array.prototype.mapToNumbers = function() {
  9. /* ... */
  10. };

String literal types

It’s not uncommon for an API to expect a specific set of strings for certain values. For instance, consider a UI library that can move elements across the screen while controlling the “easing” of the animation.

  1. ts
    declare class UIElement {
  2. animate(options: AnimationOptions): void;
  3. }
  4. interface AnimationOptions {
  5. deltaX: number;
  6. deltaY: number;
  7. easing: string; // Can be "ease-in", "ease-out", "ease-in-out"
  8. }

However, this is error prone - there is nothing stopping a user from accidentally misspelling one of the valid easing values:

  1. ts
    // No errors
  2. new UIElement().animate({ deltaX: 100, deltaY: 100, easing: "ease-inout" });

With TypeScript 1.8, we’ve introduced string literal types. These types are written the same way string literals are, but in type positions.

Users can now ensure that the type system will catch such errors. Here’s our new AnimationOptions using string literal types:

  1. ts
    interface AnimationOptions {
  2. deltaX: number;
  3. deltaY: number;
  4. easing: "ease-in" | "ease-out" | "ease-in-out";
  5. }
  6. // Error: Type '"ease-inout"' is not assignable to type '"ease-in" | "ease-out" | "ease-in-out"'
  7. new UIElement().animate({ deltaX: 100, deltaY: 100, easing: "ease-inout" });

Improved union/intersection type inference

TypeScript 1.8 improves type inference involving source and target sides that are both union or intersection types. For example, when inferring from string | string[] to string | T, we reduce the types to string[] and T, thus inferring string[] for T.

Example
  1. ts
    type Maybe<T> = T | void;
  2. function isDefined<T>(x: Maybe<T>): x is T {
  3. return x !== undefined && x !== null;
  4. }
  5. function isUndefined<T>(x: Maybe<T>): x is void {
  6. return x === undefined || x === null;
  7. }
  8. function getOrElse<T>(x: Maybe<T>, defaultValue: T): T {
  9. return isDefined(x) ? x : defaultValue;
  10. }
  11. function test1(x: Maybe<string>) {
  12. let x1 = getOrElse(x, "Undefined"); // string
  13. let x2 = isDefined(x) ? x : "Undefined"; // string
  14. let x3 = isUndefined(x) ? "Undefined" : x; // string
  15. }
  16. function test2(x: Maybe<number>) {
  17. let x1 = getOrElse(x, -1); // number
  18. let x2 = isDefined(x) ? x : -1; // number
  19. let x3 = isUndefined(x) ? -1 : x; // number
  20. }

Concatenate AMD and System modules with —outFile

Specifying --outFile in conjunction with --module amd or --module system will concatenate all modules in the compilation into a single output file containing multiple module closures.

A module name will be computed for each module based on its relative location to rootDir.

Example
  1. ts
    // file src/a.ts
  2. import * as B from "./lib/b";
  3. export function createA() {
  4. return B.createB();
  5. }
  1. ts
    // file src/lib/b.ts
  2. export function createB() {
  3. return {};
  4. }

Results in:

  1. js
    define("lib/b", ["require", "exports"], function(require, exports) {
  2. "use strict";
  3. function createB() {
  4. return {};
  5. }
  6. exports.createB = createB;
  7. });
  8. define("a", ["require", "exports", "lib/b"], function(require, exports, B) {
  9. "use strict";
  10. function createA() {
  11. return B.createB();
  12. }
  13. exports.createA = createA;
  14. });

Support for default import interop with SystemJS

Module loaders like SystemJS wrap CommonJS modules and expose then as a default ES6 import. This makes it impossible to share the definition files between the SystemJS and CommonJS implementation of the module as the module shape looks different based on the loader.

Setting the new compiler flag --allowSyntheticDefaultImports indicates that the module loader performs some kind of synthetic default import member creation not indicated in the imported .ts or .d.ts. The compiler will infer the existence of a default export that has the shape of the entire module itself.

System modules have this flag on by default.

Allow captured let/const in loops

Previously an error, now supported in TypeScript 1.8. let/const declarations within loops and captured in functions are now emitted to correctly match let/const freshness semantics.

Example
  1. ts
    let list = [];
  2. for (let i = 0; i < 5; i++) {
  3. list.push(() => i);
  4. }
  5. list.forEach(f => console.log(f()));

is compiled to:

  1. js
    var list = [];
  2. var _loop_1 = function(i) {
  3. list.push(function() {
  4. return i;
  5. });
  6. };
  7. for (var i = 0; i < 5; i++) {
  8. _loop_1(i);
  9. }
  10. list.forEach(function(f) {
  11. return console.log(f());
  12. });

And results in

  1. cmd
    0
  2. 1
  3. 2
  4. 3
  5. 4

Improved checking for for..in statements

Previously the type of a for..in variable is inferred to any; that allowed the compiler to ignore invalid uses within the for..in body.

Starting with TypeScript 1.8:

  • The type of a variable declared in a for..in statement is implicitly string.
  • When an object with a numeric index signature of type T (such as an array) is indexed by a for..in variable of a containing for..in statement for an object with a numeric index signature and without a string index signature (again such as an array), the value produced is of type T.
Example
  1. ts
    var a: MyObject[];
  2. for (var x in a) {
  3. // Type of x is implicitly string
  4. var obj = a[x]; // Type of obj is MyObject
  5. }

Modules are now emitted with a “use strict”; prologue

Modules were always parsed in strict mode as per ES6, but for non-ES6 targets this was not respected in the generated code. Starting with TypeScript 1.8, emitted modules are always in strict mode. This shouldn’t have any visible changes in most code as TS considers most strict mode errors as errors at compile time, but it means that some things which used to silently fail at runtime in your TS code, like assigning to NaN, will now loudly fail. You can reference the MDN Article on strict mode for a detailed list of the differences between strict mode and non-strict mode.

Including .js files with —allowJs

Often there are external source files in your project that may not be authored in TypeScript. Alternatively, you might be in the middle of converting a JS code base into TS, but still want to bundle all your JS code into a single file with the output of your new TS code.

.js files are now allowed as input to tsc. The TypeScript compiler checks the input .js files for syntax errors, and emits valid output based on the --target and --module flags. The output can be combined with other .ts files as well. Source maps are still generated for .js files just like with .ts files.

Custom JSX factories using —reactNamespace

Passing --reactNamespace <JSX factory Name> along with --jsx react allows for using a different JSX factory from the default React.

The new factory name will be used to call createElement and __spread functions.

Example
  1. ts
    import { jsxFactory } from "jsxFactory";
  2. var div = <div>Hello JSX!</div>;

Compiled with:

  1. shell
    tsc --jsx react --reactNamespace jsxFactory --m commonJS

Results in:

  1. js
    "use strict";
  2. var jsxFactory_1 = require("jsxFactory");
  3. var div = jsxFactory_1.jsxFactory.createElement("div", null, "Hello JSX!");

this-based type guards

TypeScript 1.8 extends user-defined type guard functions to class and interface methods.

this is T is now valid return type annotation for methods in classes and interfaces. When used in a type narowing position (e.g. if statement), the type of the call expression target object would be narrowed to T.

Example
  1. ts
    class FileSystemObject {
  2. isFile(): this is File {
  3. return this instanceof File;
  4. }
  5. isDirectory(): this is Directory {
  6. return this instanceof Directory;
  7. }
  8. isNetworked(): this is Networked & this {
  9. return this.networked;
  10. }
  11. constructor(public path: string, private networked: boolean) {}
  12. }
  13. class File extends FileSystemObject {
  14. constructor(path: string, public content: string) {
  15. super(path, false);
  16. }
  17. }
  18. class Directory extends FileSystemObject {
  19. children: FileSystemObject[];
  20. }
  21. interface Networked {
  22. host: string;
  23. }
  24. let fso: FileSystemObject = new File("foo/bar.txt", "foo");
  25. if (fso.isFile()) {
  26. fso.content; // fso is File
  27. } else if (fso.isDirectory()) {
  28. fso.children; // fso is Directory
  29. } else if (fso.isNetworked()) {
  30. fso.host; // fso is networked
  31. }

Official TypeScript NuGet package

Starting with TypeScript 1.8, official NuGet packages are available for the Typescript Compiler (tsc.exe) as well as the MSBuild integration (Microsoft.TypeScript.targets and Microsoft.TypeScript.Tasks.dll).

Stable packages are available here:

Also, a nightly NuGet package to match the nightly npm package is available on myget:

Prettier error messages from tsc

We understand that a ton of monochrome output can be a little difficult on the eyes. Colors can help discern where a message starts and ends, and these visual clues are important when error output gets overwhelming.

By just passing the --pretty command line option, TypeScript gives more colorful output with context about where things are going wrong.

Showing off pretty error messages in ConEmu

Colorization of JSX code in VS 2015

With TypeScript 1.8, JSX tags are now classified and colorized in Visual Studio 2015.

jsx

The classification can be further customized by changing the font and color settings for the VB XML color and font settings through Tools->Options->Environment->Fonts and Colors page.

The —project (-p) flag can now take any file path

The --project command line option originally could only take paths to a folder containing a tsconfig.json. Given the different scenarios for build configurations, it made sense to allow --project to point to any other compatible JSON file. For instance, a user might want to target ES2015 with CommonJS modules for Node 5, but ES5 with AMD modules for the browser. With this new work, users can easily manage two separate build targets using tsc alone without having to perform hacky workarounds like placing tsconfig.json files in separate directories.

The old behavior still remains the same if given a directory - the compiler will try to find a file in the directory named tsconfig.json.

Allow comments in tsconfig.json

It’s always nice to be able to document your configuration! tsconfig.json now accepts single and multi-line comments.

  1. ts
    {
  2. "compilerOptions": {
  3. "target": "ES2015", // running on node v5, yaay!
  4. "sourceMap": true // makes debugging easier
  5. },
  6. /*
  7. * Excluded files
  8. */
  9. "exclude": [
  10. "file.d.ts"
  11. ]
  12. }

Support output to IPC-driven files

TypeScript 1.8 allows users to use the --outFile argument with special file system entities like named pipes, devices, etc.

As an example, on many Unix-like systems, the standard output stream is accessible by the file /dev/stdout.

  1. shell
    tsc foo.ts --outFile /dev/stdout

This can be used to pipe output between commands as well.

As an example, we can pipe our emitted JavaScript into a pretty printer like pretty-js:

  1. shell
    tsc foo.ts --outFile /dev/stdout | pretty-js

Improved support for tsconfig.json in Visual Studio 2015

TypeScript 1.8 allows tsconfig.json files in all project types. This includes ASP.NET v4 projects, Console Application, and the Html Application with TypeScript project types. Further, you are no longer limited to a single tsconfig.json file but can add multiple, and each will be built as part of the project. This allows you to separate the configuration for different parts of your application without having to use multiple different projects.

Showing off tsconfig.json in Visual Studio

We also disable the project properties page when you add a tsconfig.json file. This means that all configuration changes have to be made in the tsconfig.json file itself.

A couple of limitations

  • If you add a tsconfig.json file, TypeScript files that are not considered part of that context are not compiled.
  • Apache Cordova Apps still have the existing limitation of a single tsconfig.json file, which must be in either the root or the scripts folder.
  • There is no template for tsconfig.json in most project types.