Ένας αναλυτικός οδηγός για το TypeScript Compiler API, που καλύπτει τα Abstract Syntax Trees (AST), την ανάλυση, τον μετασχηματισμό και τη δημιουργία κώδικα.
TypeScript Compiler API: Εξειδίκευση στον Χειρισμό AST και τον Μετασχηματισμό Κώδικα
Το TypeScript Compiler API παρέχει μια ισχυρή διεπαφή για την ανάλυση, τον χειρισμό και τη δημιουργία κώδικα TypeScript και JavaScript. Στον πυρήνα του βρίσκεται το Abstract Syntax Tree (AST), μια δομημένη αναπαράσταση του πηγαίου κώδικά σας. Η κατανόηση του τρόπου λειτουργίας με το AST ξεκλειδώνει δυνατότητες για την κατασκευή προηγμένων εργαλείων, όπως linters, code formatters, static analyzers και custom code generators.
Τι είναι το TypeScript Compiler API;
Το TypeScript Compiler API είναι ένα σύνολο από διεπαφές και συναρτήσεις TypeScript που εκθέτουν την εσωτερική λειτουργία του μεταγλωττιστή TypeScript. Επιτρέπει στους προγραμματιστές να αλληλεπιδρούν προγραμματιστικά με τη διαδικασία μεταγλώττισης, πηγαίνοντας πέρα από την απλή μεταγλώττιση κώδικα. Μπορείτε να το χρησιμοποιήσετε για:
- Ανάλυση Κώδικα: Επιθεώρηση της δομής του κώδικα, εντοπισμός πιθανών προβλημάτων και εξαγωγή σημασιολογικών πληροφοριών.
- Μετασχηματισμός Κώδικα: Τροποποίηση υπάρχοντος κώδικα, προσθήκη νέων χαρακτηριστικών ή αυτόματη αναδιάρθρωση (refactor) του κώδικα.
- Δημιουργία Κώδικα: Δημιουργία νέου κώδικα από την αρχή βάσει προτύπων ή άλλων δεδομένων εισόδου.
Αυτό το API είναι απαραίτητο για τη δημιουργία εξελιγμένων εργαλείων ανάπτυξης που βελτιώνουν την ποιότητα του κώδικα, αυτοματοποιούν επαναλαμβανόμενες εργασίες και ενισχύουν την παραγωγικότητα των προγραμματιστών.
Κατανοώντας το Abstract Syntax Tree (AST)
Το AST είναι μια δενδρική αναπαράσταση της δομής του κώδικά σας. Κάθε κόμβος στο δέντρο αντιπροσωπεύει μια συντακτική κατασκευή, όπως μια δήλωση μεταβλητής, μια κλήση συνάρτησης ή μια εντολή ελέγχου ροής. Το TypeScript Compiler API παρέχει εργαλεία για τη διάσχιση του AST, την επιθεώρηση των κόμβων του και την τροποποίησή τους.
Εξετάστε αυτόν τον απλό κώδικα TypeScript:
function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("World"));
Το AST για αυτόν τον κώδικα θα αναπαριστούσε τη δήλωση της συνάρτησης, την εντολή return, το template literal, την κλήση console.log και άλλα στοιχεία του κώδικα. Η οπτικοποίηση του AST μπορεί να είναι δύσκολη, αλλά εργαλεία όπως το AST explorer (astexplorer.net) μπορούν να βοηθήσουν. Αυτά τα εργαλεία σας επιτρέπουν να εισάγετε κώδικα και να δείτε το αντίστοιχο AST σε μια φιλική προς τον χρήστη μορφή. Η χρήση του AST Explorer θα σας βοηθήσει να κατανοήσετε το είδος της δομής κώδικα που θα χειρίζεστε.
Βασικοί Τύποι Κόμβων AST
Το TypeScript Compiler API ορίζει διάφορους τύπους κόμβων AST, καθένας από τους οποίους αντιπροσωπεύει μια διαφορετική συντακτική κατασκευή. Ακολουθούν ορισμένοι συνήθεις τύποι κόμβων:
- SourceFile: Αντιπροσωπεύει ένα ολόκληρο αρχείο TypeScript.
- FunctionDeclaration: Αντιπροσωπεύει τον ορισμό μιας συνάρτησης.
- VariableDeclaration: Αντιπροσωπεύει μια δήλωση μεταβλητής.
- Identifier: Αντιπροσωπεύει ένα αναγνωριστικό (π.χ., όνομα μεταβλητής, όνομα συνάρτησης).
- StringLiteral: Αντιπροσωπεύει ένα αλφαριθμητικό literal.
- CallExpression: Αντιπροσωπεύει μια κλήση συνάρτησης.
- ReturnStatement: Αντιπροσωπεύει μια εντολή return.
Κάθε τύπος κόμβου έχει ιδιότητες που παρέχουν πληροφορίες σχετικά με το αντίστοιχο στοιχείο του κώδικα. Για παράδειγμα, ένας κόμβος `FunctionDeclaration` μπορεί να έχει ιδιότητες για το όνομά του, τις παραμέτρους, τον τύπο επιστροφής και το σώμα του.
Ξεκινώντας με το Compiler API
Για να ξεκινήσετε να χρησιμοποιείτε το Compiler API, θα πρέπει να εγκαταστήσετε το TypeScript και να έχετε μια βασική κατανόηση της σύνταξης του TypeScript. Ακολουθεί ένα απλό παράδειγμα που δείχνει πώς να διαβάσετε ένα αρχείο TypeScript και να εκτυπώσετε το 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, // Target ECMAScript version
true // SetParentNodes: true to retain parent references in the 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);
Επεξήγηση:
- Εισαγωγή Modules: Εισάγει το module `typescript` και το module `fs` για λειτουργίες συστήματος αρχείων.
- Ανάγνωση Πηγαίου Αρχείου: Διαβάζει το περιεχόμενο ενός αρχείου TypeScript με όνομα `example.ts`. Θα χρειαστεί να δημιουργήσετε ένα αρχείο `example.ts` για να λειτουργήσει αυτό.
- Δημιουργία SourceFile: Δημιουργεί ένα αντικείμενο `SourceFile`, το οποίο αντιπροσωπεύει τη ρίζα του AST. Η συνάρτηση `ts.createSourceFile` αναλύει τον πηγαίο κώδικα και δημιουργεί το AST.
- Εκτύπωση AST: Ορίζει μια αναδρομική συνάρτηση `printAST` που διασχίζει το AST και εκτυπώνει το είδος κάθε κόμβου.
- Κλήση printAST: Καλεί την `printAST` για να ξεκινήσει η εκτύπωση του AST από τον ριζικό κόμβο `SourceFile`.
Για να εκτελέσετε αυτόν τον κώδικα, αποθηκεύστε τον ως αρχείο `.ts` (π.χ., `ast-example.ts`), δημιουργήστε ένα αρχείο `example.ts` με κάποιον κώδικα TypeScript και, στη συνέχεια, μεταγλωττίστε και εκτελέστε τον κώδικα:
tsc ast-example.ts
node ast-example.js
Αυτό θα εκτυπώσει το AST του αρχείου `example.ts` στην κονσόλα. Η έξοδος θα δείξει την ιεραρχία των κόμβων και τους τύπους τους. Για παράδειγμα, μπορεί να δείξει `FunctionDeclaration`, `Identifier`, `Block` και άλλους τύπους κόμβων.
Διάσχιση του AST
Το Compiler API παρέχει διάφορους τρόπους για τη διάσχιση του AST. Ο απλούστερος είναι η χρήση της μεθόδου `forEachChild`, όπως φαίνεται στο προηγούμενο παράδειγμα. Αυτή η μέθοδος επισκέπτεται κάθε παιδικό κόμβο ενός δεδομένου κόμβου.
Για πιο σύνθετα σενάρια διάσχισης, μπορείτε να χρησιμοποιήσετε το μοτίβο `Visitor`. Ένας visitor είναι ένα αντικείμενο που ορίζει μεθόδους που καλούνται για συγκεκριμένους τύπους κόμβων. Αυτό σας επιτρέπει να προσαρμόσετε τη διαδικασία διάσχισης και να εκτελέσετε ενέργειες με βάση τον τύπο του κόμβου.
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(`Found identifier: ${node.text}`);
}
ts.forEachChild(node, n => this.visit(n));
}
}
const visitor = new IdentifierVisitor();
visitor.visit(sourceFile);
Επεξήγηση:
- Κλάση IdentifierVisitor: Ορίζει μια κλάση `IdentifierVisitor` με μια μέθοδο `visit`.
- Μέθοδος Visit: Η μέθοδος `visit` ελέγχει αν ο τρέχων κόμβος είναι `Identifier`. Αν είναι, εκτυπώνει το κείμενο του αναγνωριστικού. Στη συνέχεια, καλεί αναδρομικά την `ts.forEachChild` για να επισκεφθεί τους παιδικούς κόμβους.
- Δημιουργία Visitor: Δημιουργεί ένα στιγμιότυπο του `IdentifierVisitor`.
- Έναρξη Διάσχισης: Καλεί τη μέθοδο `visit` στο `SourceFile` για να ξεκινήσει η διάσχιση.
Αυτό το παράδειγμα δείχνει πώς να βρείτε όλα τα αναγνωριστικά στο AST. Μπορείτε να προσαρμόσετε αυτό το μοτίβο για να βρείτε άλλους τύπους κόμβων και να εκτελέσετε διαφορετικές ενέργειες.
Μετασχηματισμός του AST
Η πραγματική δύναμη του Compiler API έγκειται στην ικανότητά του να μετασχηματίζει το AST. Μπορείτε να τροποποιήσετε το AST για να αλλάξετε τη δομή και τη συμπεριφορά του κώδικά σας. Αυτή είναι η βάση για τα εργαλεία αναδιάρθρωσης κώδικα, τους γεννήτορες κώδικa και άλλα προηγμένα εργαλεία.
Για να μετασχηματίσετε το AST, θα χρειαστεί να χρησιμοποιήσετε τη συνάρτηση `ts.transform`. Αυτή η συνάρτηση δέχεται ένα `SourceFile` και μια λίστα συναρτήσεων `TransformerFactory`. Ένα `TransformerFactory` είναι μια συνάρτηση που δέχεται ένα `TransformationContext` και επιστρέφει μια συνάρτηση `Transformer`. Η συνάρτηση `Transformer` είναι υπεύθυνη για την επίσκεψη και τον μετασχηματισμό των κόμβων στο AST.
Ακολουθεί ένα απλό παράδειγμα που δείχνει πώς να προσθέσετε ένα σχόλιο στην αρχή ενός αρχείου 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)) {
// Create a leading comment
const comment = ts.addSyntheticLeadingComment(
node,
ts.SyntaxKind.MultiLineCommentTrivia,
" This file was automatically transformed ",
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);
Επεξήγηση:
- TransformerFactory: Ορίζει μια συνάρτηση `TransformerFactory` που επιστρέφει μια συνάρτηση `Transformer`.
- Transformer: Η συνάρτηση `Transformer` ελέγχει αν ο τρέχων κόμβος είναι `SourceFile`. Αν είναι, προσθέτει ένα αρχικό σχόλιο στον κόμβο χρησιμοποιώντας το `ts.addSyntheticLeadingComment`.
- ts.transform: Καλεί την `ts.transform` για να εφαρμόσει τον μετασχηματισμό στο `SourceFile`.
- Printer: Δημιουργεί ένα αντικείμενο `Printer` για τη δημιουργία κώδικα από το μετασχηματισμένο AST.
- Εκτύπωση και Εγγραφή: Εκτυπώνει τον μετασχηματισμένο κώδικα και τον γράφει σε ένα νέο αρχείο με όνομα `example.transformed.ts`.
Αυτό το παράδειγμα δείχνει έναν απλό μετασχηματισμό, αλλά μπορείτε να χρησιμοποιήσετε το ίδιο μοτίβο για να εκτελέσετε πιο σύνθετους μετασχηματισμούς, όπως την αναδιάρθρωση κώδικα, την προσθήκη εντολών καταγραφής (logging) ή τη δημιουργία τεκμηρίωσης.
Προηγμένες Τεχνικές Μετασχηματισμού
Ακολουθούν ορισμένες προηγμένες τεχνικές μετασχηματισμού που μπορείτε να χρησιμοποιήσετε με το Compiler API:
- Δημιουργία Νέων Κόμβων: Χρησιμοποιήστε τις συναρτήσεις `ts.createXXX` για να δημιουργήσετε νέους κόμβους AST. Για παράδειγμα, η `ts.createVariableDeclaration` δημιουργεί έναν νέο κόμβο δήλωσης μεταβλητής.
- Αντικατάσταση Κόμβων: Αντικαταστήστε υπάρχοντες κόμβους με νέους χρησιμοποιώντας τη συνάρτηση `ts.visitEachChild`.
- Προσθήκη Κόμβων: Προσθέστε νέους κόμβους στο AST χρησιμοποιώντας τις συναρτήσεις `ts.updateXXX`. Για παράδειγμα, η `ts.updateBlock` ενημερώνει μια εντολή block με νέες εντολές.
- Αφαίρεση Κόμβων: Αφαιρέστε κόμβους από το AST επιστρέφοντας `undefined` από τη συνάρτηση μετασχηματισμού.
Δημιουργία Κώδικα
Μετά τον μετασχηματισμό του AST, θα χρειαστεί να δημιουργήσετε κώδικα από αυτό. Το Compiler API παρέχει ένα αντικείμενο `Printer` για αυτόν τον σκοπό. Ο `Printer` δέχεται ένα AST και δημιουργεί μια αναπαράσταση του κώδικα σε μορφή αλφαριθμητικού.
Η συνάρτηση `ts.createPrinter` δημιουργεί ένα αντικείμενο `Printer`. Μπορείτε να διαμορφώσετε τον printer με διάφορες επιλογές, όπως τον χαρακτήρα νέας γραμμής που θα χρησιμοποιηθεί και το αν θα εκπέμπονται σχόλια.
Η μέθοδος `printer.printFile` δέχεται ένα `SourceFile` και επιστρέφει μια αναπαράσταση του κώδικα σε μορφή αλφαριθμητικού. Στη συνέχεια, μπορείτε να γράψετε αυτό το αλφαριθμητικό σε ένα αρχείο.
Πρακτικές Εφαρμογές του Compiler API
Το TypeScript Compiler API έχει πολλές πρακτικές εφαρμογές στην ανάπτυξη λογισμικού. Ακολουθούν μερικά παραδείγματα:
- Linters: Δημιουργήστε προσαρμοσμένους linters για την επιβολή προτύπων κωδικοποίησης και τον εντοπισμό πιθανών προβλημάτων στον κώδικά σας.
- Code Formatters: Δημιουργήστε μορφοποιητές κώδικα για την αυτόματη μορφοποίηση του κώδικά σας σύμφωνα με έναν συγκεκριμένο οδηγό στυλ.
- Static Analyzers: Αναπτύξτε στατικούς αναλυτές για τον εντοπισμό σφαλμάτων, ευπαθειών ασφαλείας και σημείων συμφόρησης απόδοσης στον κώδικά σας.
- Code Generators: Δημιουργήστε κώδικα από πρότυπα ή άλλη είσοδο, αυτοματοποιώντας επαναλαμβανόμενες εργασίες και μειώνοντας τον επαναλαμβανόμενο κώδικα (boilerplate). Για παράδειγμα, δημιουργία API clients ή σχημάτων βάσεων δεδομένων από ένα αρχείο περιγραφής.
- Εργαλεία Refactoring: Κατασκευάστε εργαλεία αναδιάρθρωσης για αυτόματη μετονομασία μεταβλητών, εξαγωγή συναρτήσεων ή μετακίνηση κώδικα μεταξύ αρχείων.
- Αυτοματοποίηση Διεθνοποίησης (i18n): Εξάγετε αυτόματα μεταφράσιμα αλφαριθμητικά από τον κώδικα TypeScript και δημιουργήστε αρχεία τοπικοποίησης για διαφορετικές γλώσσες. Για παράδειγμα, ένα εργαλείο θα μπορούσε να σαρώσει τον κώδικα για αλφαριθμητικά που περνούν σε μια συνάρτηση `translate()` και να τα προσθέσει αυτόματα σε ένα αρχείο πόρων μετάφρασης.
Παράδειγμα: Δημιουργία ενός Απλού Linter
Ας δημιουργήσουμε έναν απλό linter που ελέγχει για αχρησιμοποίητες μεταβλητές στον κώδικα TypeScript. Αυτός ο linter θα εντοπίζει μεταβλητές που δηλώνονται αλλά δεν χρησιμοποιούνται ποτέ.
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("Unused variables:");
unusedVariables.forEach(variable => console.log(`- ${variable}`));
} else {
console.log("No unused variables found.");
}
Επεξήγηση:
- Συνάρτηση findUnusedVariables: Ορίζει μια συνάρτηση `findUnusedVariables` που δέχεται ένα `SourceFile` ως είσοδο.
- Σύνολο usedVariables: Δημιουργεί ένα `Set` για την αποθήκευση των ονομάτων των χρησιμοποιημένων μεταβλητών.
- Συνάρτηση visit: Ορίζει μια αναδρομική συνάρτηση `visit` που διασχίζει το AST και προσθέτει τα ονόματα όλων των αναγνωριστικών στο σύνολο `usedVariables`.
- Συνάρτηση checkVariableDeclaration: Ορίζει μια αναδρομική συνάρτηση `checkVariableDeclaration` που ελέγχει αν μια δήλωση μεταβλητής είναι αχρησιμοποίητη. Αν είναι, προσθέτει το όνομα της μεταβλητής στον πίνακα `unusedVariables`.
- Επιστροφή unusedVariables: Επιστρέφει έναν πίνακα που περιέχει τα ονόματα τυχόν αχρησιμοποίητων μεταβλητών.
- Έξοδος: Εκτυπώνει τις αχρησιμοποίητες μεταβλητές στην κονσόλα.
Αυτό το παράδειγμα δείχνει έναν απλό linter. Μπορείτε να το επεκτείνετε για να ελέγξετε για άλλα πρότυπα κωδικοποίησης και να εντοπίσετε άλλα πιθανά προβλήματα στον κώδικά σας. Για παράδειγμα, θα μπορούσατε να ελέγξετε για αχρησιμοποίητες εισαγωγές (imports), υπερβολικά σύνθετες συναρτήσεις ή πιθανές ευπάθειες ασφαλείας. Το κλειδί είναι να κατανοήσετε πώς να διασχίσετε το AST και να εντοπίσετε τους συγκεκριμένους τύπους κόμβων που σας ενδιαφέρουν.
Βέλτιστες Πρακτικές και Σκέψεις
- Κατανόηση του AST: Αφιερώστε χρόνο για να κατανοήσετε τη δομή του AST. Χρησιμοποιήστε εργαλεία όπως το AST explorer για να οπτικοποιήσετε το AST του κώδικά σας.
- Χρήση Type Guards: Χρησιμοποιήστε type guards (`ts.isXXX`) για να βεβαιωθείτε ότι εργάζεστε με τους σωστούς τύπους κόμβων.
- Εξέταση της Απόδοσης: Οι μετασχηματισμοί AST μπορεί να είναι υπολογιστικά δαπανηροί. Βελτιστοποιήστε τον κώδικά σας για να ελαχιστοποιήσετε τον αριθμό των κόμβων που επισκέπτεστε και μετασχηματίζετε.
- Διαχείριση Σφαλμάτων: Διαχειριστείτε τα σφάλματα με χάρη. Το Compiler API μπορεί να προκαλέσει εξαιρέσεις εάν προσπαθήσετε να εκτελέσετε μη έγκυρες λειτουργίες στο AST.
- Ενδελεχής Έλεγχος: Ελέγξτε τους μετασχηματισμούς σας ενδελεχώς για να βεβαιωθείτε ότι παράγουν τα επιθυμητά αποτελέσματα και δεν εισάγουν νέα σφάλματα.
- Χρήση Υπαρχουσών Βιβλιοθηκών: Εξετάστε το ενδεχόμενο να χρησιμοποιήσετε υπάρχουσες βιβλιοθήκες που παρέχουν αφαιρέσεις υψηλότερου επιπέδου πάνω από το Compiler API. Αυτές οι βιβλιοθήκες μπορούν να απλοποιήσουν συνήθεις εργασίες και να μειώσουν την ποσότητα του κώδικα που πρέπει να γράψετε. Παραδείγματα περιλαμβάνουν τις `ts-morph` και `typescript-eslint`.
Συμπέρασμα
Το TypeScript Compiler API είναι ένα ισχυρό εργαλείο για τη δημιουργία προηγμένων εργαλείων ανάπτυξης. Κατανοώντας πώς να εργαστείτε με το AST, μπορείτε να δημιουργήσετε linters, code formatters, static analyzers και άλλα εργαλεία που βελτιώνουν την ποιότητα του κώδικα, αυτοματοποιούν επαναλαμβανόμενες εργασίες και ενισχύουν την παραγωγικότητα των προγραμματιστών. Αν και το API μπορεί να είναι πολύπλοκο, τα οφέλη από την εξοικείωση με αυτό είναι σημαντικά. Αυτός ο ολοκληρωμένος οδηγός παρέχει μια βάση για την εξερεύνηση και την αποτελεσματική χρήση του Compiler API στα έργα σας. Θυμηθείτε να αξιοποιείτε εργαλεία όπως το AST Explorer, να χειρίζεστε προσεκτικά τους τύπους κόμβων και να ελέγχετε ενδελεχώς τους μετασχηματισμούς σας. Με πρακτική και αφοσίωση, μπορείτε να ξεκλειδώσετε το πλήρες δυναμικό του TypeScript Compiler API και να δημιουργήσετε καινοτόμες λύσεις για το τοπίο της ανάπτυξης λογισμικού.
Περαιτέρω Εξερεύνηση:
- Τεκμηρίωση του TypeScript Compiler API: [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/)
- Βιβλιοθήκη ts-morph: [https://ts-morph.com/](https://ts-morph.com/)
- typescript-eslint: [https://typescript-eslint.io/](https://typescript-eslint.io/)