מדריך מקיף ל-TypeScript Compiler API, הכולל עצי תחביר מופשטים (AST), ניתוח קוד, טרנספורמציה ויצירה עבור מפתחים בינלאומיים.
TypeScript Compiler API: שליטה במניפולציה של AST ושינוי קוד
ה-TypeScript Compiler API מספק ממשק עוצמתי לניתוח, מניפולציה ויצירה של קוד TypeScript ו-JavaScript. בליבו נמצא עץ התחביר המופשט (AST), ייצוג מובנה של קוד המקור שלך. הבנה כיצד לעבוד עם ה-AST פותחת יכולות לבניית כלי עבודה מתקדמים, כגון כלי לינטינג, מעצבי קוד, מנתחים סטטיים ומחוללי קוד מותאמים אישית.
מהו TypeScript Compiler API?
ה-TypeScript Compiler API הוא אוסף של ממשקי TypeScript ופונקציות החושפות את הפעולות הפנימיות של קומפיילר TypeScript. הוא מאפשר למפתחים ליצור אינטראקציה תוכנתית עם תהליך הקומפילציה, מעבר לפשוט לקמפל קוד. אתה יכול להשתמש בו כדי:
- לנתח קוד: לבדוק את מבנה הקוד, לזהות בעיות פוטנציאליות ולחלץ מידע סמנטי.
- לשנות קוד: לשנות קוד קיים, להוסיף תכונות חדשות או לשכתב קוד באופן אוטומטי.
- ליצור קוד: ליצור קוד חדש מאפס על סמך תבניות או קלט אחר.
API זה חיוני לבניית כלי פיתוח מתוחכמים המשפרים את איכות הקוד, אוטומטיים משימות חוזרות ומגבירים את פרודוקטיביות המפתחים.
הבנת עץ התחביר המופשט (AST)
ה-AST הוא ייצוג דמוי עץ של מבנה הקוד שלך. כל צומת בעץ מייצג מבנה תחבירי, כגון הצהרת משתנה, קריאה לפונקציה או הצהרת זרימת בקרה. ה-TypeScript Compiler API מספק כלים לחצות את ה-AST, לבדוק את הצמתים שלו ולשנות אותם.
שקול את קוד TypeScript הפשוט הזה:
function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("World"));
ה-AST עבור קוד זה ייצג את הצהרת הפונקציה, הצהרת ההחזרה, הטמפלייט ליטרל, קריאת console.log ואלמנטים אחרים בקוד. הדמיה של ה-AST יכולה להיות מאתגרת, אבל כלים כמו AST explorer (astexplorer.net) יכולים לעזור. כלים אלה מאפשרים לך להזין קוד ולראות את ה-AST המתאים שלו בפורמט ידידותי למשתמש. שימוש ב-AST Explorer יעזור לך להבין את סוג מבנה הקוד שאתה הולך לתפעל.
סוגי צמתי מפתח ב-AST
ה-TypeScript Compiler API מגדיר סוגים שונים של צמתי AST, שכל אחד מהם מייצג מבנה תחבירי שונה. הנה כמה סוגי צמתי נפוצים:
- SourceFile: מייצג קובץ TypeScript שלם.
- FunctionDeclaration: מייצג הגדרת פונקציה.
- VariableDeclaration: מייצג הצהרת משתנה.
- Identifier: מייצג מזהה (לדוגמה, שם משתנה, שם פונקציה).
- StringLiteral: מייצג מחרוזת ליטרלית.
- CallExpression: מייצג קריאה לפונקציה.
- ReturnStatement: מייצג הצהרת החזרה.
לכל סוג צומת יש מאפיינים המספקים מידע על רכיב הקוד המתאים. לדוגמה, לצומת `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);
הסבר:
- ייבוא מודולים: מייבא את המודול `typescript` ואת המודול `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`. מבקר הוא אובייקט המגדיר מתודות שיופעלו עבור סוגי צמתים ספציפיים. זה מאפשר לך להתאים אישית את תהליך החציה ולבצע פעולות המבוססות על סוג הצומת.
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` כדי לבקר בצמתי הילדים.
- יצירת מבקר: יוצר מופע של ה-`IdentifierVisitor`.
- התחלת חציה: קורא למתודה `visit` על ה-`SourceFile` כדי להתחיל את החציה.
דוגמה זו מדגימה כיצד למצוא את כל המזהים ב-AST. אתה יכול להתאים את התבנית הזו כדי למצוא סוגי צמתים אחרים ולבצע פעולות שונות.
שינוי ה-AST
העוצמה האמיתית של ה-Compiler API טמונה ביכולתו לשנות את ה-AST. אתה יכול לשנות את ה-AST כדי לשנות את המבנה וההתנהגות של הקוד שלך. זה הבסיס לכלי שכתוב קוד, מחוללי קוד וכלי עבודה מתקדמים אחרים.
כדי לשנות את ה-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`.
דוגמה זו מדגימה טרנספורמציה פשוטה, אבל אתה יכול להשתמש באותה תבנית כדי לבצע טרנספורמציות מורכבות יותר, כגון שכתוב קוד, הוספת הצהרות רישום או יצירת תיעוד.
טכניקות טרנספורמציה מתקדמות
הנה כמה טכניקות טרנספורמציה מתקדמות שבהן תוכל להשתמש עם ה-Compiler API:
- יצירת צמתים חדשים: השתמש בפונקציות `ts.createXXX` כדי ליצור צמתי AST חדשים. לדוגמה, `ts.createVariableDeclaration` יוצר צומת הצהרת משתנה חדש.
- החלפת צמתים: החלף צמתים קיימים בצמתים חדשים באמצעות הפונקציה `ts.visitEachChild`.
- הוספת צמתים: הוסף צמתים חדשים ל-AST באמצעות הפונקציות `ts.updateXXX`. לדוגמה, `ts.updateBlock` מעדכן הצהרת בלוק עם הצהרות חדשות.
- הסרת צמתים: הסר צמתים מה-AST על ידי החזרת `undefined` מפונקציית הטרנספורמציה.
יצירת קוד
לאחר שינוי ה-AST, תצטרך ליצור קוד ממנו. ה-Compiler API מספק אובייקט `Printer` למטרה זו. ה-`Printer` מקבל AST ומייצר ייצוג מחרוזתי של הקוד.
הפונקציה `ts.createPrinter` יוצרת אובייקט `Printer`. אתה יכול להגדיר את המדפסת עם אפשרויות שונות, כגון תו השורה החדשה לשימוש והאם לפלוט הערות.
המתודה `printer.printFile` מקבלת `SourceFile` ומחזירה ייצוג מחרוזתי של הקוד. לאחר מכן תוכל לכתוב מחרוזת זו לקובץ.
יישומים מעשיים של ה-Compiler API
ל-TypeScript Compiler API יש יישומים מעשיים רבים בפיתוח תוכנה. הנה כמה דוגמאות:
- לינטרים: בנה לינטרים מותאמים אישית כדי לאכוף תקני קידוד ולזהות בעיות פוטנציאליות בקוד שלך.
- מעצבי קוד: צור מעצבי קוד כדי לעצב אוטומטית את הקוד שלך בהתאם למדריך סגנון ספציפי.
- מנתחים סטטיים: פתח מנתחים סטטיים כדי לזהות באגים, נקודות תורפה אבטחה ובצווארי בקבוק ביצועים בקוד שלך.
- מחוללי קוד: צור קוד מתבניות או קלט אחר, אוטומציה של משימות חוזרות ונשנות והפחתת קוד boilerplate. לדוגמה, יצירת לקוחות API או סכימות מסד נתונים מקובץ תיאור.
- כלי שכתוב קוד: בנה כלי שכתוב קוד כדי לשנות אוטומטית שמות של משתנים, לחלץ פונקציות או להעביר קוד בין קבצים.
- אוטומציה של בינאום (i18n): חלץ אוטומטית מחרוזות ניתנות לתרגום מקוד TypeScript שלך וצור קבצי לוקליזציה עבור שפות שונות. לדוגמה, כלי יכול לסרוק קוד עבור מחרוזות המועברות לפונקציה `translate()` ולהוסיף אותן אוטומטית לקובץ משאבי תרגום.
דוגמה: בניית לינטר פשוט
בוא ניצור לינטר פשוט הבודק משתנים לא בשימוש בקוד 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
);
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: מחזירה מערך המכיל את השמות של כל המשתנים שאינם בשימוש.
- פלט: מדפיס את המשתנים שאינם בשימוש לקונסולה.
דוגמה זו מדגימה לינטר פשוט. אתה יכול להרחיב אותו כדי לבדוק תקני קידוד אחרים ולזהות בעיות פוטנציאליות אחרות בקוד שלך. לדוגמה, אתה יכול לבדוק ייבוא שאינו בשימוש, פונקציות מורכבות מדי או נקודות תורפה פוטנציאליות באבטחה. המפתח הוא להבין כיצד לחצות את ה-AST ולזהות את סוגי הצמתים הספציפיים שבהם אתה מתעניין.
שיטות עבודה מומלצות ושיקולים
- הבנת ה-AST: השקיעו זמן בהבנת המבנה של ה-AST. השתמש בכלים כמו AST explorer כדי להמחיש את ה-AST של הקוד שלך.
- שימוש בשומרי סוג: השתמש בשומרי סוג (`ts.isXXX`) כדי לוודא שאתה עובד עם סוגי הצמתים הנכונים.
- שיקול ביצועים: טרנספורמציות AST יכולות להיות יקרות מבחינה חישובית. בצע אופטימיזציה של הקוד שלך כדי למזער את מספר הצמתים שאתה מבקר ומשנה.
- טיפול בשגיאות: טפל בשגיאות בחן. ה-Compiler API יכול לזרוק חריגים אם תנסה לבצע פעולות לא חוקיות על ה-AST.
- בדיקה יסודית: בדוק את הטרנספורמציות שלך ביסודיות כדי לוודא שהן מייצרות את התוצאות הרצויות ואינן מציגות באגים חדשים.
- שימוש בספריות קיימות: שקול להשתמש בספריות קיימות המספקות הפשטות ברמה גבוהה יותר על ה-Compiler API. ספריות אלה יכולות לפשט משימות נפוצות ולהפחית את כמות הקוד שאתה צריך לכתוב. דוגמאות כוללות `ts-morph` ו-`typescript-eslint`.
מסקנה
ה-TypeScript Compiler API הוא כלי רב עוצמה לבניית כלי פיתוח מתקדמים. על ידי הבנת כיצד לעבוד עם ה-AST, אתה יכול ליצור לינטרים, מעצבי קוד, מנתחים סטטיים וכלי עבודה אחרים המשפרים את איכות הקוד, אוטומטיים משימות חוזרות ונשנות ומגבירים את פרודוקטיביות המפתחים. למרות שה-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/)