Guide complet de l'API du compilateur TypeScript : AST, analyse, transformation et génération de code pour développeurs internationaux.
API du Compilateur TypeScript : Maîtriser la Manipulation et la Transformation d'AST
L'API du compilateur TypeScript fournit une interface puissante pour analyser, manipuler et générer du code TypeScript et JavaScript. En son cœur se trouve l'Arbre Syntaxique Abstrait (AST), une représentation structurée de votre code source. Comprendre comment travailler avec l'AST débloque des capacités pour construire des outils avancés, tels que des linters, des formateurs de code, des analyseurs statiques et des générateurs de code personnalisés.
Qu'est-ce que l'API du Compilateur TypeScript ?
L'API du compilateur TypeScript est un ensemble d'interfaces et de fonctions TypeScript qui exposent le fonctionnement interne du compilateur TypeScript. Elle permet aux développeurs d'interagir par programme avec le processus de compilation, allant au-delà de la simple compilation de code. Vous pouvez l'utiliser pour :
- Analyser le Code : Inspecter la structure du code, identifier les problèmes potentiels et extraire des informations sémantiques.
- Transformer le Code : Modifier le code existant, ajouter de nouvelles fonctionnalités ou refactoriser le code automatiquement.
- Générer du Code : Créer du nouveau code à partir de zéro en se basant sur des modèles ou d'autres entrées.
Cette API est essentielle pour construire des outils de développement sophistiqués qui améliorent la qualité du code, automatisent les tâches répétitives et augmentent la productivité des développeurs.
Comprendre l'Arbre Syntaxique Abstrait (AST)
L'AST est une représentation arborescente de la structure de votre code. Chaque nœud de l'arbre représente une construction syntaxique, telle qu'une déclaration de variable, un appel de fonction ou une instruction de contrôle de flux. L'API du compilateur TypeScript fournit des outils pour parcourir l'AST, inspecter ses nœuds et les modifier.
Considérez ce code TypeScript simple :
function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("World"));
L'AST pour ce code représenterait la déclaration de la fonction, l'instruction de retour, le littéral de chaîne, l'appel à console.log, et d'autres éléments du code. Visualiser l'AST peut être difficile, mais des outils comme AST explorer (astexplorer.net) peuvent aider. Ces outils vous permettent de saisir du code et de voir son AST correspondant dans un format convivial. L'utilisation d'AST Explorer vous aidera à comprendre le type de structure de code que vous manipulerez.
Types Clés de Nœuds AST
L'API du compilateur TypeScript définit divers types de nœuds AST, chacun représentant une construction syntaxique différente. Voici quelques types de nœuds courants :
- SourceFile : Représente un fichier TypeScript entier.
- FunctionDeclaration : Représente une définition de fonction.
- VariableDeclaration : Représente une déclaration de variable.
- Identifier : Représente un identifiant (par exemple, nom de variable, nom de fonction).
- StringLiteral : Représente un littéral de chaîne.
- CallExpression : Représente un appel de fonction.
- ReturnStatement : Représente une instruction de retour.
Chaque type de nœud possède des propriétés qui fournissent des informations sur l'élément de code correspondant. Par exemple, un nœud `FunctionDeclaration` peut avoir des propriétés pour son nom, ses paramètres, son type de retour et son corps.
Démarrer avec l'API du Compilateur
Pour commencer à utiliser l'API du compilateur, vous devrez installer TypeScript et avoir une compréhension de base de la syntaxe TypeScript. Voici un exemple simple qui montre comment lire un fichier TypeScript et imprimer son AST :
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015, // Version ECMAScript cible
true // SetParentNodes: true pour conserver les références parentes dans l'AST
);
function printAST(node: ts.Node, indent = 0) {
const indentStr = " ".repeat(indent);
console.log(`${indentStr}${ts.SyntaxKind[node.kind]}`);
node.forEachChild(child => printAST(child, indent + 1));
}
printAST(sourceFile);
Explication :
- Importer les Modules : Importe le module `typescript` et le module `fs` pour les opérations sur le système de fichiers.
- Lire le Fichier Source : Lit le contenu d'un fichier TypeScript nommé `example.ts`. Vous devrez créer un fichier `example.ts` pour que cela fonctionne.
- Créer SourceFile : Crée un objet `SourceFile`, qui représente la racine de l'AST. La fonction `ts.createSourceFile` analyse le code source et génère l'AST.
- Imprimer l'AST : Définit une fonction récursive `printAST` qui parcourt l'AST et imprime le type (kind) de chaque nœud.
- Appeler printAST : Appelle `printAST` pour commencer à imprimer l'AST à partir du nœud racine `SourceFile`.
Pour exécuter ce code, enregistrez-le dans un fichier `.ts` (par exemple, `ast-example.ts`), créez un fichier `example.ts` avec du code TypeScript, puis compilez et exécutez le code :
tsc ast-example.ts
node ast-example.js
Cela imprimera l'AST de votre fichier `example.ts` dans la console. La sortie montrera la hiérarchie des nœuds et leurs types. Par exemple, elle pourrait montrer `FunctionDeclaration`, `Identifier`, `Block`, et d'autres types de nœuds.
Parcourir l'AST
L'API du compilateur fournit plusieurs façons de parcourir l'AST. La plus simple est d'utiliser la méthode `forEachChild`, comme montré dans l'exemple précédent. Cette méthode visite chaque nœud enfant d'un nœud donné.
Pour des scénarios de parcours plus complexes, vous pouvez utiliser un modèle de `Visiteur`. Un visiteur est un objet qui définit des méthodes à appeler pour des types de nœuds spécifiques. Cela vous permet de personnaliser le processus de parcours et d'effectuer des actions basées sur le type de nœud.
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
class IdentifierVisitor {
visit(node: ts.Node) {
if (ts.isIdentifier(node)) {
console.log(`Identifiant trouvé : ${node.text}`);
}
ts.forEachChild(node, n => this.visit(n));
}
}
const visitor = new IdentifierVisitor();
visitor.visit(sourceFile);
Explication :
- Classe IdentifierVisitor : Définit une classe `IdentifierVisitor` avec une méthode `visit`.
- Méthode Visit : La méthode `visit` vérifie si le nœud actuel est un `Identifier`. Si c'est le cas, elle imprime le texte de l'identifiant. Ensuite, elle appelle récursivement `ts.forEachChild` pour visiter les nœuds enfants.
- Créer un Visiteur : Crée une instance de `IdentifierVisitor`.
- Démarrer le Parcours : Appelle la méthode `visit` sur le `SourceFile` pour démarrer le parcours.
Cet exemple montre comment trouver tous les identifiants dans l'AST. Vous pouvez adapter ce modèle pour trouver d'autres types de nœuds et effectuer différentes actions.
Transformer l'AST
La véritable puissance de l'API du compilateur réside dans sa capacité à transformer l'AST. Vous pouvez modifier l'AST pour changer la structure et le comportement de votre code. C'est la base des outils de refactorisation de code, des générateurs de code et d'autres outils avancés.
Pour transformer l'AST, vous devrez utiliser la fonction `ts.transform`. Cette fonction prend un `SourceFile` et une liste de fonctions `TransformerFactory`. Un `TransformerFactory` est une fonction qui prend un `TransformationContext` et retourne une fonction `Transformer`. La fonction `Transformer` est responsable de la visite et de la transformation des nœuds dans l'AST.
Voici un exemple simple qui montre comment ajouter un commentaire au début d'un fichier TypeScript :
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
const transformerFactory: ts.TransformerFactory = context => {
return transformer => {
return node => {
if (ts.isSourceFile(node)) {
// Créer un commentaire principal
const comment = ts.addSyntheticLeadingComment(
node,
ts.SyntaxKind.MultiLineCommentTrivia,
" Ce fichier a été transformé automatiquement ",
true // hasTrailingNewLine
);
return node;
}
return node;
};
};
};
const { transformed } = ts.transform(sourceFile, [transformerFactory]);
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed
});
const result = printer.printFile(transformed[0]);
fs.writeFileSync("example.transformed.ts", result);
Explication :
- TransformerFactory : Définit une fonction `TransformerFactory` qui retourne une fonction `Transformer`.
- Transformer : La fonction `Transformer` vérifie si le nœud actuel est un `SourceFile`. Si c'est le cas, elle ajoute un commentaire principal au nœud à l'aide de `ts.addSyntheticLeadingComment`.
- ts.transform : Appelle `ts.transform` pour appliquer la transformation au `SourceFile`.
- Printer : Crée un objet `Printer` pour générer du code à partir de l'AST transformé.
- Imprimer et Écrire : Imprime le code transformé et l'écrit dans un nouveau fichier nommé `example.transformed.ts`.
Cet exemple démontre une transformation simple, mais vous pouvez utiliser le même modèle pour effectuer des transformations plus complexes, telles que la refactorisation de code, l'ajout d'instructions de journalisation ou la génération de documentation.
Techniques de Transformation Avancées
Voici quelques techniques de transformation avancées que vous pouvez utiliser avec l'API du compilateur :
- Créer de Nouveaux Nœuds : Utilisez les fonctions `ts.createXXX` pour créer de nouveaux nœuds AST. Par exemple, `ts.createVariableDeclaration` crée un nouveau nœud de déclaration de variable.
- Remplacer des Nœuds : Remplacez les nœuds existants par de nouveaux nœuds en utilisant la fonction `ts.visitEachChild`.
- Ajouter des Nœuds : Ajoutez de nouveaux nœuds à l'AST en utilisant les fonctions `ts.updateXXX`. Par exemple, `ts.updateBlock` met à jour une instruction de bloc avec de nouvelles instructions.
- Supprimer des Nœuds : Supprimez des nœuds de l'AST en retournant `undefined` de la fonction de transformation.
Génération de Code
Après avoir transformé l'AST, vous devrez générer du code à partir de celui-ci. L'API du compilateur fournit un objet `Printer` à cet effet. Le `Printer` prend un AST et génère une représentation textuelle du code.
La fonction `ts.createPrinter` crée un objet `Printer`. Vous pouvez configurer le `Printer` avec diverses options, telles que le caractère de nouvelle ligne à utiliser et s'il faut émettre des commentaires.
La méthode `printer.printFile` prend un `SourceFile` et retourne une représentation textuelle du code. Vous pouvez ensuite écrire cette chaîne dans un fichier.
Applications Pratiques de l'API du Compilateur
L'API du compilateur TypeScript a de nombreuses applications pratiques dans le développement logiciel. Voici quelques exemples :
- Linters : Créez des linters personnalisés pour faire respecter les normes de codage et identifier les problèmes potentiels dans votre code.
- Formateurs de Code : Créez des formateurs de code pour formater automatiquement votre code selon un guide de style spécifique.
- Analyseurs Statiques : Développez des analyseurs statiques pour détecter les bugs, les vulnérabilités de sécurité et les goulots d'étranglement de performance dans votre code.
- Générateurs de Code : Générez du code à partir de modèles ou d'autres entrées, en automatisant les tâches répétitives et en réduisant le code répétitif. Par exemple, générer des clients API ou des schémas de base de données à partir d'un fichier de description.
- Outils de Refactorisation : Créez des outils de refactorisation pour renommer automatiquement les variables, extraire des fonctions ou déplacer du code entre les fichiers.
- Automatisation de l'Internationalisation (i18n) : Extrayez automatiquement les chaînes traduisibles de votre code TypeScript et générez des fichiers de localisation pour différentes langues. Par exemple, un outil pourrait scanner le code à la recherche de chaînes passées à une fonction `translate()` et les ajouter automatiquement à un fichier de ressources de traduction.
Exemple : Créer un Linter Simple
Créons un linter simple qui vérifie les variables inutilisées dans le code TypeScript. Ce linter identifiera les variables qui sont déclarées mais jamais utilisées.
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
function findUnusedVariables(sourceFile: ts.SourceFile) {
const usedVariables = new Set();
function visit(node: ts.Node) {
if (ts.isIdentifier(node)) {
usedVariables.add(node.text);
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
const unusedVariables: string[] = [];
function checkVariableDeclaration(node: ts.Node) {
if (ts.isVariableDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
const variableName = node.name.text;
if (!usedVariables.has(variableName)) {
unusedVariables.push(variableName);
}
}
ts.forEachChild(node, checkVariableDeclaration);
}
checkVariableDeclaration(sourceFile);
return unusedVariables;
}
const unusedVariables = findUnusedVariables(sourceFile);
if (unusedVariables.length > 0) {
console.log("Variables inutilisées :");
unusedVariables.forEach(variable => console.log(`- ${variable}`));
} else {
console.log("Aucune variable inutilisée trouvée.");
}
Explication :
- Fonction findUnusedVariables : Définit une fonction `findUnusedVariables` qui prend un `SourceFile` en entrée.
- Set usedVariables : Crée un `Set` pour stocker les noms des variables utilisées.
- Fonction visit : Définit une fonction récursive `visit` qui parcourt l'AST et ajoute les noms de tous les identifiants au set `usedVariables`.
- Fonction checkVariableDeclaration : Définit une fonction récursive `checkVariableDeclaration` qui vérifie si une déclaration de variable est inutilisée. Si c'est le cas, elle ajoute le nom de la variable au tableau `unusedVariables`.
- Retourner unusedVariables : Retourne un tableau contenant les noms de toutes les variables inutilisées.
- Sortie : Imprime les variables inutilisées dans la console.
Cet exemple démontre un linter simple. Vous pouvez l'étendre pour vérifier d'autres normes de codage et identifier d'autres problèmes potentiels dans votre code. Par exemple, vous pourriez vérifier les importations inutilisées, les fonctions trop complexes ou les vulnérabilités de sécurité potentielles. L'essentiel est de comprendre comment parcourir l'AST et identifier les types de nœuds spécifiques qui vous intéressent.
Bonnes Pratiques et Considérations
- Comprendre l'AST : Investissez du temps pour comprendre la structure de l'AST. Utilisez des outils comme AST explorer pour visualiser l'AST de votre code.
- Utiliser des Type Guards : Utilisez des `Type Guards` (`ts.isXXX`) pour vous assurer que vous travaillez avec les bons types de nœuds.
- Considérer les Performances : Les transformations de l'AST peuvent être coûteuses en calcul. Optimisez votre code pour minimiser le nombre de nœuds que vous visitez et transformez.
- Gérer les Erreurs : Gérez les erreurs avec grâce. L'API du compilateur peut lever des exceptions si vous essayez d'effectuer des opérations invalides sur l'AST.
- Tester Soigneusement : Testez vos transformations méticuleusement pour vous assurer qu'elles produisent les résultats souhaités et n'introduisent pas de nouveaux bugs.
- Utiliser des Bibliothèques Existantes : Envisagez d'utiliser des bibliothèques existantes qui fournissent des abstractions de plus haut niveau sur l'API du compilateur. Ces bibliothèques peuvent simplifier les tâches courantes et réduire la quantité de code que vous devez écrire. Les exemples incluent `ts-morph` et `typescript-eslint`.
Conclusion
L'API du compilateur TypeScript est un outil puissant pour construire des outils de développement avancés. En comprenant comment travailler avec l'AST, vous pouvez créer des linters, des formateurs de code, des analyseurs statiques et d'autres outils qui améliorent la qualité du code, automatisent les tâches répétitives et augmentent la productivité des développeurs. Bien que l'API puisse être complexe, les avantages de la maîtriser sont considérables. Ce guide complet fournit une base pour explorer et utiliser efficacement l'API du compilateur dans vos projets. N'oubliez pas de tirer parti d'outils comme AST Explorer, de gérer attentivement les types de nœuds et de tester vos transformations méticuleusement. Avec de la pratique et du dévouement, vous pouvez libérer tout le potentiel de l'API du compilateur TypeScript et construire des solutions innovantes pour le paysage du développement logiciel.
Exploration Supplémentaire :
- Documentation de l'API du Compilateur TypeScript : [https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API)
- AST Explorer : [https://astexplorer.net/](https://astexplorer.net/)
- Bibliothèque ts-morph : [https://ts-morph.com/](https://ts-morph.com/)
- typescript-eslint : [https://typescript-eslint.io/](https://typescript-eslint.io/)