IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Microsoft annonce la disponibilité de TypeScript 5.5, cette version apporte les prédicats de type inférés, les déclarations isolées
Ainsi qu'une amélioration de la fiabilité de l'éditeur

Le , par Jade Emy

406PARTAGES

6  0 
Microsoft annonce la disponibilité de TypeScript 5.5. Parmi les nouveautés : Prédicats de type inférés, réduction du flux de contrôle pour les accès indexés constants, vérification syntaxique des expressions régulières, les déclarations isolées, amélioration de la fiabilité de l'éditeur et du mode veille, et optimisation des performances et de la taille, et bien plus encore.

TypeScript est un langage qui s'appuie sur JavaScript en permettant de déclarer et de décrire des types. Écrire des types dans le code permet d'expliquer l'intention et de faire vérifier le code par d'autres outils pour détecter les erreurs comme les fautes de frappe, les problèmes avec null et undefined, et bien plus encore.

Les types alimentent également les outils d'édition de TypeScript, comme l'auto-complétion, la navigation dans le code et les refactorisations que vous pouvez voir dans des éditeurs tels que Visual Studio et VS Code. En fait, si vous écrivez du JavaScript dans l'un ou l'autre de ces éditeurs, cette expérience est alimentée par TypeScript.

Depuis la version Beta, Microsoft a ajouté la prise en charge des nouvelles méthodes ECMAScript Set. De plus, ils ont ajusté le comportement de la nouvelle vérification des expressions régulières de TypeScript pour être légèrement plus indulgent, tout en continuant à faire des erreurs sur les échappements douteux qui ne sont autorisés que par l'annexe B d'ECMAScript.

Microsoft a également ajouté et documenté encore plus d'optimisations de performance : notamment, la vérification sautée dans transpileModule et les optimisations dans la façon dont TypeScript filtre les types contextuels. Ces optimisations peuvent conduire à des temps de construction et d'itération plus rapides dans de nombreux scénarios courants.


Depuis la version candidate (RC), Microsoft est revenu temporairement sur sa nouvelle méthode de travail qui consistait à consulter le fichier package.json pour déterminer le format de module d'un fichier donné. ce changement perturbait certains flux de travail et causait une pression inattendue sur la surveillance des fichiers pour les grands projets. Dans TypeScript 5.6, Microsoft espère ramener une version plus nuancée de cette fonctionnalité, tout en cherchant à optimiser la façon de surveiller les fichiers inexistants.

Des changements de comportements sont également notables dans TypeScript 5.5. Parmi les changements, on peut noter : Désactivation des fonctionnalités obsolètes dans TypeScript 5.0, changements dans lib.d.ts, respect des extensions de fichiers et de package.json dans d'autres modes de modules, parsing plus strict pour les décorateurs, undefined n'est plus un nom de type définissable, et déclaration simplifiée de la directive de référence Emit.

Voici une liste rapide des nouveautés de TypeScript 5.5 :

  • Prédicats de type inférés
  • Réduction du flux de contrôle pour les accès indexés constants
  • La balise @import de JSDoc
  • Vérification syntaxique des expressions régulières
  • Déclarations isolées
  • La variable modèle ${configDir} pour les fichiers de configuration
  • Consultation des dépendances package.json pour la génération de fichiers de déclaration
  • Amélioration de la fiabilité de l'éditeur et du mode veille
  • Optimisation des performances et de la taille
  • Modules ECMAScript de consommation d'API plus faciles à utiliser
  • API transpileDeclaration




Prédicats de type inférés

L'analyse du flux de contrôle de TypeScript fait un excellent travail de suivi de la façon dont le type d'une variable change au fur et à mesure qu'elle se déplace dans votre code :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Bird {
    commonName: string;
    scientificName: string;
    sing(): void;
}

// Maps country names -> national bird.
// Not all nations have official birds (looking at you, Canada!)
declare const nationalBirds: Map<string, Bird>;

function makeNationalBirdCall(country: string) {
  const bird = nationalBirds.get(country);  // bird has a declared type of Bird | undefined
  if (bird) {
    bird.sing();  // bird has type Bird inside the if statement
  } else {
    // bird has type undefined here.
  }
}
En vous obligeant à gérer les cas indéfinis, TypeScript vous pousse à écrire un code plus robuste.

Dans le passé, ce type de raffinement de type était plus difficile à appliquer aux tableaux. Cela aurait été une erreur dans toutes les versions précédentes de TypeScript :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
function makeBirdCalls(countries: string[]) {
  // birds: (Bird | undefined)[]
  const birds = countries
    .map(country => nationalBirds.get(country))
    .filter(bird => bird !== undefined);

  for (const bird of birds) {
    bird.sing();  // error: 'bird' is possibly 'undefined'.
  }
}
Ce code est parfaitement correct : ils ont filtré toutes les valeurs indéfinies de la liste. Mais TypeScript n'a pas été en mesure de suivre le mouvement.

Avec TypeScript 5.5, le vérificateur de type n'a rien à redire à ce code :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
function makeBirdCalls(countries: string[]) {
  // birds: Bird[]
  const birds = countries
    .map(country => nationalBirds.get(country))
    .filter(bird => bird !== undefined);

  for (const bird of birds) {
    bird.sing();  // ok!
  }
}
Notez le type plus précis pour les birds.

Cela fonctionne parce que TypeScript déduit maintenant un prédicat de type pour la fonction de filtrage. Vous pouvez voir plus clairement ce qui se passe en la transformant en une fonction indépendante :

Code : Sélectionner tout
1
2
3
4
// function isBirdReal(bird: Bird | undefined): bird is Bird
function isBirdReal(bird: Bird | undefined) {
  return bird !== undefined;
}
bird is Bird est le prédicat de type. Cela signifie que si la fonction renvoie true, il s'agit d'un bird (si la fonction renvoie false, il s'agit d'un indéfini). Les déclarations de type pour Array.prototype.filter connaissent les prédicats de type, de sorte que le résultat net est que vous obtenez un type plus précis et que le code passe le vérificateur de type.

TypeScript déduira qu'une fonction renvoie un prédicat de type si ces conditions sont remplies :

  1. La fonction n'a pas d'annotation explicite de type de retour ou de prédicat de type.
  2. La fonction a un seul énoncé de retour et aucun retour implicite.
  3. La fonction ne modifie pas son paramètre.
  4. La fonction renvoie une expression booléenne liée à un raffinement du paramètre.


En règle générale, tout se passe comme prévu. Voici quelques autres exemples de prédicats de types déduits :

Code : Sélectionner tout
1
2
3
4
5
// const isNumber: (x: unknown) => x is number
const isNumber = (x: unknown) => typeof x === 'number';

// const isNonNullish: <T>(x: T) => x is NonNullable<T>
const isNonNullish = <T,>(x: T) => x != null;
Auparavant, TypeScript aurait simplement déduit que ces fonctions renvoient une expression booléenne. Il déduit maintenant les signatures avec des prédicats de type comme x est un nombre ou x est NonNullable<T>.

Les prédicats de type ont une sémantique « si et seulement si ». Si une fonction renvoie x is T, cela signifie que :

  1. Si la fonction renvoie un résultat vrai, x est de type T.
  2. Si la fonction renvoie faux, x n'est pas de type T.


Si vous vous attendez à ce qu'un prédicat de type soit déduit mais qu'il ne l'est pas, vous risquez de vous heurter à la deuxième règle. Ce problème se pose souvent dans le cadre des vérifications de « véracité » :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
function getClassroomAverage(students: string[], allScores: Map<string, number>) {
  const studentScores = students
    .map(student => allScores.get(student))
    .filter(score => !!score);

  return studentScores.reduce((a, b) => a + b) / studentScores.length;
  //     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // error: Object is possibly 'undefined'.
}
TypeScript n'a pas déduit de prédicat de type pour score => !!score, et ce à juste titre : si ce prédicat retourne vrai, alors score est un nombre. Mais s'il renvoie false, alors score peut être soit indéfini, soit un nombre (en particulier, 0). Il s'agit d'un véritable bogue : si un étudiant a obtenu un zéro à l'examen, le fait de filtrer son score faussera la moyenne vers le haut. Moins d'élèves seront au-dessus de la moyenne et plus d'élèves seront tristes !

Comme dans le premier exemple, il est préférable de filtrer explicitement les valeurs indéfinies :

Code : Sélectionner tout
1
2
3
4
5
6
7
function getClassroomAverage(students: string[], allScores: Map<string, number>) {
  const studentScores = students
    .map(student => allScores.get(student))
    .filter(score => score !== undefined);

  return studentScores.reduce((a, b) => a + b) / studentScores.length;  // ok!
}
Un contrôle de véracité déduira un prédicat de type pour les types d'objets, là où il n'y a pas d'ambiguïté. Rappelez-vous que les fonctions doivent retourner un booléen pour être candidates à un prédicat de type inféré : x => !!x peut inférer un prédicat de type, mais x => x ne le fera certainement pas.

Les prédicats de type explicites continuent à fonctionner exactement comme avant. TypeScript ne vérifiera pas s'il déduit le même prédicat de type. Les prédicats de type explicites (« is ») ne sont pas plus sûrs qu'une assertion de type (« as »).

Il est possible que cette fonctionnalité casse du code existant si TypeScript déduit maintenant un type plus précis que ce que vous souhaitez. Par exemple :

Code : Sélectionner tout
1
2
3
4
5
// Previously, nums: (number | null)[]
// Now, nums: number[]
const nums = [1, 2, 3, null, 5].filter(x => x !== null);

nums.push(null);  // ok in TS 5.4, error in TS 5.5
La solution consiste à indiquer à TypeScript le type que vous souhaitez en utilisant une annotation de type explicite :

Code : Sélectionner tout
1
2
const nums: (number | null)[] = [1, 2, 3, null, 5].filter(x => x !== null);
nums.push(null);  // ok in all versions

Réduction du flux de contrôle pour les accès indexés constants

TypeScript est désormais capable de réduire les expressions de la forme obj[key] lorsque obj et key sont effectivement constants.

Code : Sélectionner tout
1
2
3
4
5
6
function f1(obj: Record<string, unknown>, key: string) {
    if (typeof obj[key] === "string") {
        // Now okay, previously was error
        obj[key].toUpperCase();
    }
}
Dans l'exemple ci-dessus, ni obj ni key ne sont jamais mutés, donc TypeScript peut limiter le type de obj[key] à string après la vérification de typeof.

La balise @import de JSDoc

Aujourd'hui, si vous voulez importer quelque chose uniquement pour vérifier le type dans un fichier JavaScript, c'est encombrant. Les développeurs JavaScript ne peuvent pas simplement importer un type nommé SomeType s'il n'est pas présent au moment de l'exécution.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ./some-module.d.ts
export interface SomeType {
    // ...
}

// ./index.js
import { SomeType } from "./some-module"; //  runtime error!

/**
 * @param {SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}
SomeType n'existera pas au moment de l'exécution, et l'importation échouera. Les développeurs peuvent à la place utiliser une importation d'espace de noms.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
import * as someModule from "./some-module";

/**
 * @param {someModule.SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}
Mais ./some-module est toujours importé à l'exécution, ce qui n'est pas forcément souhaitable.

Pour éviter cela, les développeurs devaient généralement utiliser des types import(...) dans les commentaires JSDoc.

Code : Sélectionner tout
1
2
3
4
5
6
/**
 * @param {import("./some-module").SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}
Si vous vouliez réutiliser le même type à plusieurs endroits, vous pouviez utiliser un typedef pour éviter de répéter l'importation.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
/**
 * @typedef {import("./some-module").SomeType} SomeType
 */

/**
 * @param {SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}
Cela est utile pour les utilisations locales de SomeType, mais cela devient répétitif pour de nombreuses importations et peut être un peu verbeux.

C'est pourquoi TypeScript prend désormais en charge une nouvelle balise de commentaire @import qui a la même syntaxe que les importations ECMAScript.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
/** @import { SomeType } from "some-module" */

/**
 * @param {SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}
Ici, ils ont utilisé des importations nommées. Ils auraient également pu écrire l'importation comme une importation d'espace de noms.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
/** @import * as someModule from "some-module" */

/**
 * @param {someModule.SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}
Comme il ne s'agit que de commentaires JSDoc, ils n'affectent en rien le comportement à l'exécution.

Vérification syntaxique des expressions régulières

Jusqu'à présent, TypeScript a généralement ignoré la plupart des expressions régulières dans le code. En effet, les expressions régulières ont techniquement une grammaire extensible et TypeScript n'a jamais fait d'effort pour compiler les expressions régulières dans les versions antérieures de JavaScript. Néanmoins, cela signifiait que de nombreux problèmes courants n'étaient pas découverts dans les expressions régulières et qu'ils se transformaient en erreurs au moment de l'exécution ou échouaient silencieusement.

Mais TypeScript effectue désormais un contrôle syntaxique de base sur les expressions régulières !

Code : Sélectionner tout
1
2
3
4
let myRegex = /@robot(\s+(please|immediately)))? do some task/;
//                                            ~
// error!
// Unexpected ')'. Did you mean to escape it with backslash?
Il s'agit d'un exemple simple, mais cette vérification peut permettre de détecter de nombreuses erreurs courantes. En fait, le contrôle de TypeScript va un peu plus loin que les contrôles syntaxiques. Par exemple, TypeScript peut maintenant détecter les problèmes liés à des références arrière qui n'existent pas.

Code : Sélectionner tout
1
2
3
4
5
let myRegex = /@typedef \{import\((.+)\)\.([a-zA-Z_]+)\} \3/u;
//                                                        ~
// error!
// This backreference refers to a group that does not exist.
// There are only 2 capturing groups in this regular expression.
Il en va de même pour les groupes de capture nommés.

Code : Sélectionner tout
1
2
3
4
let myRegex = /@typedef \{import\((?<importPath>.+)\)\.(?<importedEntity>[a-zA-Z_]+)\} \k<namedImport>/;
//                                                                                        ~~~~~~~~~~~
// error!
// There is no capturing group named 'namedImport' in this regular expression.
La vérification de TypeScript est désormais également consciente de l'utilisation de certaines fonctionnalités RegExp lorsque celles-ci sont plus récentes que votre version cible d'ECMAScript. Par exemple, si on utilise des groupes de capture nommés comme ci-dessus dans une cible ES5, on obtiendra une erreur.

Code : Sélectionner tout
1
2
3
4
let myRegex = /@typedef \{import\((?<importPath>.+)\)\.(?<importedEntity>[a-zA-Z_]+)\} \k<importedEntity>/;
//                                  ~~~~~~~~~~~~         ~~~~~~~~~~~~~~~~
// error!
// Named capturing groups are only available when targeting 'ES2018' or later.
Il en va de même pour certains drapeaux d'expressions régulières.

Notez que la prise en charge des expressions régulières par TypeScript est limitée aux expressions régulières littérales. Si vous essayez d'appeler new RegExp avec une chaîne littérale, TypeScript ne vérifiera pas la chaîne fournie.

Prise en charge des nouvelles méthodes ECMAScript Set

TypeScript 5.5 déclare de nouvelles méthodes proposées pour le type ECMAScript Set.

Certaines de ces méthodes, comme union, intersection, différence et symmetricDifference, prennent un autre [CSet[/C] et renvoient un nouveau Set comme résultat. Les autres méthodes,...
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.

Une erreur dans cette actualité ? Signalez-nous-la !