release

3.6

Повышение типобезопасности генераторов

До текущей версии такие конструкции как генераторы (generators) имели недоработки касающиеся определения типа данных возвращаемых, как из, так и во внутрь генератора, значений.

ts
// пример со значением возвращающимся из генератора

// function generator(): IterableIterator<"Done" | 100>
function* generator() {
    yield 100;

    return `Done`;
}

let iterator = generator(); // let iterator: IterableIterator<"Done" | 100>
let result = iterator.next(); // let result: IteratorResult<"Done" | 100>

/**
 * Поскольку генератор перешел в завершенное состояние
 * значение result.value может принадлежать исключительно
 * к типу string ...
 */
if (result.done) {
    /**
     * ... тем не менее, вывод типов определяет его
     * как тип объединение (Union) string | number
     */
    let value = result.value; // let value: string | number
}
```

`````ts
// пример со значением возвращаемым в генератор

type Greeter = {
    greet(): void;
};

// function generator(): IterableIterator<undefined>
function* generator() {
    /**
     * В строке - let greeter: Greeter = yield;
     * предполагается, что возвращенное из внешнего кода
     * значение будет принадлежать к типу Greeter...
     *
     */
    let greeter: Greeter = yield;
    greeter.greet();
}

let iterator = generator(); // let iterator: IterableIterator<undefined>
iterator.next();
/**
 * Строка ниже приведёт к ошибке во время выполнения
 * внутри  генератора при вызове метода greeter.greet();
 * поскольку возращенное значение принадлежит к типу number,
 * в, то время как ожидается тип Greeter.
 */
iterator.next(123); // Error, runtime error
```

Начиная с версии _TypeScript_ `3.6` описанные выше недостатки были устранены.

`````ts
// пример со значением возвращающимся из генератора

/**
 *  <v3.6: function generator(): IterableIterator<"Done" | 100>
 * >=v3.6: function generator(): Generator<number, string, unknown>
 */
function* generator() {
    yield 100;

    return `Done`;
}

/**
 *  <v3.6: let iterator: IterableIterator<"Done" | 100>
 * >=v3.6: let iterator: Generator<number, string, unknown>
 */
let iterator = generator();

/**
 *  <v3.6: let result: IteratorResult<"Done" | 100>
 * >=v3.6: let result: IteratorResult<number, string>
 */
let result = iterator.next();

if (result.done) {
    /**
     *  <v3.6: let value: string | number
     * >=v3.6: let value: string
     */
    let value = result.value;
}
```

`````ts
// пример со значением возвращаемым в генератор

type Greeter = {
    greet(): void;
};

/**
 *  <v3.6: function generator(): IterableIterator<undefined>
 * >=v3.6: function generator(): Generator<undefined, void, Greeter>
 */
function* generator() {
    let greeter: Greeter = yield;
    greeter.greet();
}

/**
 *  <v3.6: let iterator: IterableIterator<undefined>
 * >=v3.6: let iterator: Generator<undefined, void, Greeter>
 */
let iterator = generator();
iterator.next();
iterator.next(123); // Error! Argument of type '[123]' is not assignable to parameter of type '[] | [Greeter]'.
```

Подобное стало возможно благодаря добавлению шести новых типов перечисленных ниже, которые вы также можете использовать при работе с генераторами.

`````ts
interface Iterator<T, TReturn = any, TNext = undefined> {
    /** ... */
}
type IteratorResult<T, TReturn = any> =
    | IteratorYieldResult<T>
    | IteratorReturnResult<TReturn>;
interface IteratorReturnResult<TReturn> {
    /** ... */
}
interface IteratorYieldResult<TYield> {
    /** ... */
}

interface Generator<T = unknown, TReturn = any, TNext = unknown>
    extends Iterator<T, TReturn, TNext> {
    /** ... */
}
interface GeneratorFunction {
    /** ... */
}
interface GeneratorFunctionConstructor {
    /** ... */
}
```

И напоследок будет не лишним ещё раз взглянуть на очень простой и информативный пример более эффективной работы с генераторами.

`````ts
/**
 * Generator<number, string, boolean>
 * или по другому
 * Generator<
 *  возвращаемое с помощью оператора yield  значение,
 *  возвращаемое с помощью оператора return значение,
 *  передаваемое в метод next, то есть возвращаемое в генератор, значение
 * >
 */
function* counter(): Generator<number, string, boolean> {
    let i = 0;

    while (true) {
        if (yield i++) {
            console.log(
                `[if]  Block if in counter generator.
                       Variable value "i": ${i}`
            );

            break;
        }
    }

    return 'Done';
}

let iterator = counter();
let result = iterator.next();

while (!result.done) {
    let returnedFromGeneratorValue = result.value;
    let passedToGeneratorValue = returnedFromGeneratorValue === 3;

    console.log(
        `[out] Returned from generator value: ${returnedFromGeneratorValue}`
    );
    console.log(`[in]  Passed to generator  value: ${passedToGeneratorValue}`);

    result = iterator.next(passedToGeneratorValue);
}

console.log(
    `[end] Return from generator resultant value: ${result.value.toUpperCase()}`
);

/**
 * "[out] Returned from generator value: 0"
 * "[in]  Passed to generator  value: false"
 * "[out] Returned from generator value: 1"
 * "[in]  Passed to generator  value: false"
 * "[out] Returned from generator value: 2"
 * "[in]  Passed to generator  value: true"
 * "[if]  Block if in counter generator.
          Variable value "i": 3"
 * "[end] Return from generator resultant value: DONE"
 */
```

Добавление хелпера имитирующего механизм spread array при компиляции в ecmascript ниже версии 6 при неактивном флаге --downlevelIteration

Начиная с версии v3.6, хелпер, генерирующийся при компиляции такой конструкции, как array spread ([...Array(5)]), в ECMAScript ниже 6 версии, при неактивном флаге --downlevelIteration, претерпел кардинальные изменения. Но обо всем по порядку.

До TypeScript версии v3.6 при компиляции кода в версию ниже es6, для имитации механизма spread применённого к массиву, генерировался код объединяющий массивы с помощью его метода .concat() -

ts
let array = [0, 1, ...[2, 3]]; // этот код es6+ компилировался в..
var array = [0, 1].concat([2, 3]); // ..этот es5 код
```

Но имитация подобным образом не соответствует поведению предполагаемого спецификацией `es6` при использовании массива созданного с помощью конструктора, которому при вызове передали в качестве единственного аргумента числовое значение.

`````ts
let array = [0, 1, ...Array(3), 2, 3]; // этот код es6+ компилировался в..
var array = [0, 1].concat(Array(2), [2, 3]); // ..этот es5 код
```

Получение экземпляра массива подобным образом приводит к созданию объекта массива с заданной длиной, равной значению переданного в качестве аргумента, а также полным отсутствием элементов и следовательно ассоциированных с ними ключей (индексов массива).

`````ts
Array(3); // [empty × 3]

['a', 'b', 'c']; // ['a', 'b', 'c']

/**
 * У объекта массива созданного с помощью конструктора
 * длина равна переданному аргументу, то есть 3, но
 * ключи представляющие индекс массива, также как и
 * элементы - отсутствуют.
 */
Array(3); // псевдо объект массива {length: 3}

/**
 * У объекта массива созданного с помощью литерала массива
 * длина равняется количеству его элементов, то есть 3, а также
 * существуют ключи (0, 1, 2) ассоциированные с элементами ('a','b','c').
 */
['a', 'b', 'c']; // псевдо объект массива {length: 3, 0: 'a', 1: 'b', 2: 'c'}
```

Не будет лишним напомнить, что элементы у массива созданного с помощью конструктора буквально полностью отсутствуют, а не имеют значение `undefined`, как может показаться. При обращении к элементам по индексу входящего в диапазон установленного его длиной, `undefined` возвращается не потому, что хранится в качестве элемента, а потому, что `undefined` возвращается всегда при обращении к несуществующему ключу объекта.

`````ts
// случай с объектом созданного при помощи литерала

({ prop: 'value' }['prop']); // value - обращение к существующему ключу prop
({}['prop']); // undefined - обращение к не существующему ключу prop

// случай с массивом созданного при помощи литерала

['a', 'b', 'c'][0]; // a - обращение к существующему ключу 0
['a', 'b', 'c'][10]; // undefined - обращение к не существующему ключу 10

// случай с массивом созданного при помощи конструктора

Array(3)[0]; // undefined - обращение к не существующему ключу 0
```

Отсутствие ключей является причиной несоответствия количества реальных итераций и длины массива при работе с ними. Методы массива (`.forEach()`, `.map()` и др.) и итерирующие конструкции (`for...in\for...of`)проверяют ключи с помощью метода `.hasOwnProperty(key)` доставшегося по наследству от базового типа `Object`.

`````ts
/**
 * псевдо объект массива {length: 3}
 * Нет ключей - нет итераций!
 * Вывод в консоль: (пусто)
 */
Array(3).forEach(() => console.log(`iteration`));

/**
 * псевдо объект массива {length: 3}
 * Нет ключей - нет итераций!
 * Вывод в консоль: (пусто)
 */
for (let item of Array(3)) {
    console.log(`iteration`);
}
```

Тем не менее при применении механизма `spread` к массиву в `es6+` создает несуществующие элементы и заполняет их значениями `undefined`.

`````ts
/**
 * Длина === 7, количество итераций === 7
 */
[1, 2, ...Array(3), 3, 4]; // es6 - [1, 2, undefined, undefined, undefined, 3, 4]
```

Но скомпилированный `TypeScript` в `es < v6` код при неактивном флаге `--downlevelIteration`, до версии `v3.6` не соответствовал этому поведению, поскольку не преобразовывал отсутствующие элементы в элементы ассоциированные со значением `undefined`.

`````ts
/**
 * Длина === 7, количество итераций === 4!
 */
[1, 2, ...Array(3), 3, 4]; // es6 синтаксис компилировался в..
[1, 2].concat(Array(3), [2, 3]); // в es5 подобный код - [1, 2, empty × 3, 2, 3]
```

После того, как комьюнити обратило внимание на несоответствие в поведении, подход с нативным методом `.concat()` заменили на генерацию хелпера `__spreadArrays(array)` и тем самым исправили поведение на предполагаемое спецификацией. Отказ от нативного `.concat()` замедлил сказался на производительности, снизив её ровно в два раза.

Расширение системы типов типами представляющими структуры данных только для чтения

Начиная с версии v3.6, система типов Typescript пополнилась новыми вспомогательными типами представляющими структуры данных только для чтения (readonly). Такими типами стали ReadonlyMap<K, V> и ReadonlySet<T> и в отличии от своих прототипов они не имеют методов способных их изменить.

ts
let map: ReadonlyMap<string, number> = new Map([['key', 0]]);
let set: ReadonlySet<number> = new Set([0, 1, 2]);
```

Строковой идентификатор функции-конструктора

TypeScript, начиная с версии v3.6 реализовывает механизм ecmascript спецификации предусматривающей строковое именование функции-конструктора. Другими словами, определение метода идентификатор которого представлен в виде строки эквивалентной "constructor" расценивается как определение функции-конструктора. Если идентификатор помечается, как вычисляемый ["constructor"], то такое объявление расценивается обычным методом класса.

ts
// этот ts код...

class T {
    constructor() {
        console.log(`Constructor!`);
    }

    ['constructor']() {
        console.log(`Method with name "constructor"!`);
    }
}

let t = new T(); // output: Constructor!
t.constructor(); // output: Method with name "constructor"!

// ...будет скомпилирован в этот js код

class T {
    constructor() {
        console.log(`Constructor!`);
    }
    ['constructor']() {
        console.log(`Method with name "constructor"!`);
    }
}
let t = new T(); // output: Constructor!
t.constructor(); // output: Method with name "constructor"!
```

Расширение вывода об ошибках при работе с Promise

Поскольку работа с Promise является очень важной частью повседневной разработки, TypeScript, начиная с версии v3.6, расширила информирование при возникновении ошибок связанными с ними, а также били добавлены механизмы их быстрого устранения.

ts
interface Person {
    name: string;
    age: number;
}

declare function getPersonData(): Promise<Person>;
declare function printPersonInfo(personData: Person): void;

async function main() {
    /**
     * До v3.6 возникала ошибка говорящая, что 
     * тип Promise<Person> не соответствует типу Person
     *
     * Argument of type 'Promise<Person>' is not assignable
     * to parameter of type 'Person'.
     * Type 'Promise<Person>' is missing the following properties
     * from type 'Person': name, age ts(2345)
     *
     *
     * После v3.6 вывод расширили предложением пофиксить
     * ошибку добавлением ключевого слова await
     *
     * Did you forget to use 'await'?
     *
     */
    printPersonInfo(getPersonData()); // Error
    printPersonInfo(await getPersonData()); // Ok
}

async function getPersonData(): Promise<Person> {
    /**
     * Также предлагается пофиксить ошибку путем
     * добавления ключевого слова await
     *
     * Property 'json' does not exist on type 'Promise<Response>'.ts(2339)
     * Did you forget to use 'await'?
     */
    return fetch(``).json(); // Error
    return (await fetch(``)).json(); // Ok
}
```

Слияние одноименных классов и функций из окружающего контекста

Начиная с v3.6 был реализован механизм позволяющий сливать воедино одноименные классы и функции находящиеся в одном окружающем контексте.

ts
declare class Point {
    x: number;
    y: number;

    constructor(x: number, y: number);
}

declare function Point(x: number, y: number): Point;
```

Начиная с `v3.7` компилятор будет прибегать к данной возможности при генерации `.d.ts` файлов из `JavaScript` кода.

Добавлена поддержка Unicode для идентификаторов

Начиная с версии v3.6 в TypeScript, для кода компилируемого в --target es2015 и выше, была добавлена поддержка символов unicode используемых в качестве идентификаторов синтаксических конструкций.

ts
/**
 * До v3.6:
 *      Error, Variable declaration expected.
 *
 * Начиная с v3.6 для --target es2015 и выше
 *      Ok
 */
const 𝓱𝓮𝓵𝓵𝓸 = 'world';
```

Поддержка import.meta для SystemJS

В TypeScript v3.6 была добавлена возможность трансформации import.meta в context.meta, что является необходимым функционалом при работе с такой модульной системой, как SystemJS (--module system).

ts
// этот код .ts ...
console.log(import.meta);

// ...преобразуется в этот .js код
System.register([], function (exports_1, context_1) {
    'use strict';
    var __moduleName = context_1 && context_1.id;
    return {
        setters: [],
        execute: function () {
            console.log(context_1.meta);
        },
    };
});
```

get и set в окружающем контексте

Начиная с версии v3.6, TypeScript получил возможность объявлять get/set в окружающем контексте (declare class ID {} или в файлах декларациях .d.ts).

ts
// Начиная с v3.6 - Ok
declare class T {
    get field(): string;
    set field(value: string): void;
}
```

Кроме того, начиная с версии `v3.7` компилятор начнет использовать данный функционал при генерации `.d.ts` файлов.

Api для поддержки --build и --incremental

С недавних пор в TypeScript, благодаря добавлению функционала скрывающегося за флагами --build и --incremental, появилась возможность создавать ссылки на другие проекты и генерировать метаинформацию о предыдущих компиляциях, позволяющую включать в последующие компиляции только те файлы, которые подверглись изменениям. Данный функционал позволил значительно увеличить скорость разработки, но до этого момента не был доступен для совместного использования с такими сборщиками, как например Gulp или Webpack. Начиная с версии v3.6, разработчики компилятора TypeScript добавили новое api позволяющее создателям плагинов для сторонних сборщиков использовать данный, увеличивающий скорость компиляции, механизм.

Интеллектуальное определение синтаксиса авто-импорта

Поскольку TypeScript позволяет напрямую работать с JavaScript кодом, который может содержать множество вариантов экспорта конструкций (стандарты ECScript, CommonJS, AMD, SystemJS), возникают разногласия по поводу определения синтаксиса экспорта в коде .ts при авто-импорте. Начиная с версии v3.6, компилятору была добавлена возможность автоматического определения синтаксиса используемого при авто-импорте для каждого отдельного случая.