[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"similar-itsdouges--typescript-transformer-handbook":3,"tool-itsdouges--typescript-transformer-handbook":64},[4,17,27,35,48,56],{"id":5,"name":6,"github_repo":7,"description_zh":8,"stars":9,"difficulty_score":10,"last_commit_at":11,"category_tags":12,"status":16},1381,"everything-claude-code","affaan-m\u002Feverything-claude-code","everything-claude-code 是一套专为 AI 编程助手（如 Claude Code、Codex、Cursor 等）打造的高性能优化系统。它不仅仅是一组配置文件，而是一个经过长期实战打磨的完整框架，旨在解决 AI 代理在实际开发中面临的效率低下、记忆丢失、安全隐患及缺乏持续学习能力等核心痛点。\n\n通过引入技能模块化、直觉增强、记忆持久化机制以及内置的安全扫描功能，everything-claude-code 能显著提升 AI 在复杂任务中的表现，帮助开发者构建更稳定、更智能的生产级 AI 代理。其独特的“研究优先”开发理念和针对 Token 消耗的优化策略，使得模型响应更快、成本更低，同时有效防御潜在的攻击向量。\n\n这套工具特别适合软件开发者、AI 研究人员以及希望深度定制 AI 工作流的技术团队使用。无论您是在构建大型代码库，还是需要 AI 协助进行安全审计与自动化测试，everything-claude-code 都能提供强大的底层支持。作为一个曾荣获 Anthropic 黑客大奖的开源项目，它融合了多语言支持与丰富的实战钩子（hooks），让 AI 真正成长为懂上",142651,2,"2026-04-06T23:34:12",[13,14,15],"开发框架","Agent","语言模型","ready",{"id":18,"name":19,"github_repo":20,"description_zh":21,"stars":22,"difficulty_score":23,"last_commit_at":24,"category_tags":25,"status":16},4487,"LLMs-from-scratch","rasbt\u002FLLMs-from-scratch","LLMs-from-scratch 是一个基于 PyTorch 的开源教育项目，旨在引导用户从零开始一步步构建一个类似 ChatGPT 的大型语言模型（LLM）。它不仅是同名技术著作的官方代码库，更提供了一套完整的实践方案，涵盖模型开发、预训练及微调的全过程。\n\n该项目主要解决了大模型领域“黑盒化”的学习痛点。许多开发者虽能调用现成模型，却难以深入理解其内部架构与训练机制。通过亲手编写每一行核心代码，用户能够透彻掌握 Transformer 架构、注意力机制等关键原理，从而真正理解大模型是如何“思考”的。此外，项目还包含了加载大型预训练权重进行微调的代码，帮助用户将理论知识延伸至实际应用。\n\nLLMs-from-scratch 特别适合希望深入底层原理的 AI 开发者、研究人员以及计算机专业的学生。对于不满足于仅使用 API，而是渴望探究模型构建细节的技术人员而言，这是极佳的学习资源。其独特的技术亮点在于“循序渐进”的教学设计：将复杂的系统工程拆解为清晰的步骤，配合详细的图表与示例，让构建一个虽小但功能完备的大模型变得触手可及。无论你是想夯实理论基础，还是为未来研发更大规模的模型做准备",90106,3,"2026-04-06T11:19:32",[15,26,14,13],"图像",{"id":28,"name":29,"github_repo":30,"description_zh":31,"stars":32,"difficulty_score":10,"last_commit_at":33,"category_tags":34,"status":16},3704,"NextChat","ChatGPTNextWeb\u002FNextChat","NextChat 是一款轻量且极速的 AI 助手，旨在为用户提供流畅、跨平台的大模型交互体验。它完美解决了用户在多设备间切换时难以保持对话连续性，以及面对众多 AI 模型不知如何统一管理的痛点。无论是日常办公、学习辅助还是创意激发，NextChat 都能让用户随时随地通过网页、iOS、Android、Windows、MacOS 或 Linux 端无缝接入智能服务。\n\n这款工具非常适合普通用户、学生、职场人士以及需要私有化部署的企业团队使用。对于开发者而言，它也提供了便捷的自托管方案，支持一键部署到 Vercel 或 Zeabur 等平台。\n\nNextChat 的核心亮点在于其广泛的模型兼容性，原生支持 Claude、DeepSeek、GPT-4 及 Gemini Pro 等主流大模型，让用户在一个界面即可自由切换不同 AI 能力。此外，它还率先支持 MCP（Model Context Protocol）协议，增强了上下文处理能力。针对企业用户，NextChat 提供专业版解决方案，具备品牌定制、细粒度权限控制、内部知识库整合及安全审计等功能，满足公司对数据隐私和个性化管理的高标准要求。",87618,"2026-04-05T07:20:52",[13,15],{"id":36,"name":37,"github_repo":38,"description_zh":39,"stars":40,"difficulty_score":10,"last_commit_at":41,"category_tags":42,"status":16},2268,"ML-For-Beginners","microsoft\u002FML-For-Beginners","ML-For-Beginners 是由微软推出的一套系统化机器学习入门课程，旨在帮助零基础用户轻松掌握经典机器学习知识。这套课程将学习路径规划为 12 周，包含 26 节精炼课程和 52 道配套测验，内容涵盖从基础概念到实际应用的完整流程，有效解决了初学者面对庞大知识体系时无从下手、缺乏结构化指导的痛点。\n\n无论是希望转型的开发者、需要补充算法背景的研究人员，还是对人工智能充满好奇的普通爱好者，都能从中受益。课程不仅提供了清晰的理论讲解，还强调动手实践，让用户在循序渐进中建立扎实的技能基础。其独特的亮点在于强大的多语言支持，通过自动化机制提供了包括简体中文在内的 50 多种语言版本，极大地降低了全球不同背景用户的学习门槛。此外，项目采用开源协作模式，社区活跃且内容持续更新，确保学习者能获取前沿且准确的技术资讯。如果你正寻找一条清晰、友好且专业的机器学习入门之路，ML-For-Beginners 将是理想的起点。",85013,"2026-04-06T11:09:19",[26,43,44,45,14,46,15,13,47],"数据工具","视频","插件","其他","音频",{"id":49,"name":50,"github_repo":51,"description_zh":52,"stars":53,"difficulty_score":23,"last_commit_at":54,"category_tags":55,"status":16},3128,"ragflow","infiniflow\u002Fragflow","RAGFlow 是一款领先的开源检索增强生成（RAG）引擎，旨在为大语言模型构建更精准、可靠的上下文层。它巧妙地将前沿的 RAG 技术与智能体（Agent）能力相结合，不仅支持从各类文档中高效提取知识，还能让模型基于这些知识进行逻辑推理和任务执行。\n\n在大模型应用中，幻觉问题和知识滞后是常见痛点。RAGFlow 通过深度解析复杂文档结构（如表格、图表及混合排版），显著提升了信息检索的准确度，从而有效减少模型“胡编乱造”的现象，确保回答既有据可依又具备时效性。其内置的智能体机制更进一步，使系统不仅能回答问题，还能自主规划步骤解决复杂问题。\n\n这款工具特别适合开发者、企业技术团队以及 AI 研究人员使用。无论是希望快速搭建私有知识库问答系统，还是致力于探索大模型在垂直领域落地的创新者，都能从中受益。RAGFlow 提供了可视化的工作流编排界面和灵活的 API 接口，既降低了非算法背景用户的上手门槛，也满足了专业开发者对系统深度定制的需求。作为基于 Apache 2.0 协议开源的项目，它正成为连接通用大模型与行业专有知识之间的重要桥梁。",77062,"2026-04-04T04:44:48",[14,26,13,15,46],{"id":57,"name":58,"github_repo":59,"description_zh":60,"stars":61,"difficulty_score":23,"last_commit_at":62,"category_tags":63,"status":16},519,"PaddleOCR","PaddlePaddle\u002FPaddleOCR","PaddleOCR 是一款基于百度飞桨框架开发的高性能开源光学字符识别工具包。它的核心能力是将图片、PDF 等文档中的文字提取出来，转换成计算机可读取的结构化数据，让机器真正“看懂”图文内容。\n\n面对海量纸质或电子文档，PaddleOCR 解决了人工录入效率低、数字化成本高的问题。尤其在人工智能领域，它扮演着连接图像与大型语言模型（LLM）的桥梁角色，能将视觉信息直接转化为文本输入，助力智能问答、文档分析等应用场景落地。\n\nPaddleOCR 适合开发者、算法研究人员以及有文档自动化需求的普通用户。其技术优势十分明显：不仅支持全球 100 多种语言的识别，还能在 Windows、Linux、macOS 等多个系统上运行，并灵活适配 CPU、GPU、NPU 等各类硬件。作为一个轻量级且社区活跃的开源项目，PaddleOCR 既能满足快速集成的需求，也能支撑前沿的视觉语言研究，是处理文字识别任务的理想选择。",74991,"2026-04-06T23:16:49",[15,26,13,46],{"id":65,"github_repo":66,"name":67,"description_en":68,"description_zh":69,"ai_summary_zh":70,"readme_en":71,"readme_zh":72,"quickstart_zh":73,"use_case_zh":74,"hero_image_url":75,"owner_login":76,"owner_name":77,"owner_avatar_url":78,"owner_bio":79,"owner_company":80,"owner_location":81,"owner_email":82,"owner_twitter":83,"owner_website":84,"owner_url":85,"languages":86,"stars":99,"forks":100,"last_commit_at":101,"license":82,"difficulty_score":10,"env_os":102,"env_gpu":103,"env_ram":102,"env_deps":104,"category_tags":112,"github_topics":113,"view_count":10,"oss_zip_url":82,"oss_zip_packed_at":82,"status":16,"created_at":117,"updated_at":118,"faqs":119,"releases":150},4821,"itsdouges\u002Ftypescript-transformer-handbook","typescript-transformer-handbook","📘 A comprehensive handbook on how to create transformers for TypeScript with code examples.","typescript-transformer-handbook 是一本专为 TypeScript 开发者编写的实战指南，旨在系统性地讲解如何编写自定义代码转换器（Transformers）。在 TypeScript 编译过程中，开发者有时需要深度干预代码的转换逻辑，例如自动注入代码、优化语法结构或实现特定的领域特定语言（DSL），而官方文档往往缺乏具体的实操细节。这份手册正是为了解决这一痛点，填补了从理论概念到工程落地之间的空白。\n\n它非常适合希望深入理解 TypeScript 编译器内部机制、需要定制编译流程的高级前端工程师及工具链开发者阅读。内容不仅涵盖了抽象语法树（AST）的基础概念和编译阶段解析，更提供了大量可运行的代码示例，详细演示了如何遍历节点、修改语法树、管理作用域以及处理复杂的类型检查逻辑。\n\n其独特的技术亮点在于对 Transformer API 的深度剖析，包括如何安全地替换节点、动态添加导入声明、重命名绑定以及在 Webpack 或 ttypescript 等主流构建工具中集成自定义转换器。此外，手册还分享了组合转换器、抛出友好语法错误等实用技巧，并介绍了专门的测","typescript-transformer-handbook 是一本专为 TypeScript 开发者编写的实战指南，旨在系统性地讲解如何编写自定义代码转换器（Transformers）。在 TypeScript 编译过程中，开发者有时需要深度干预代码的转换逻辑，例如自动注入代码、优化语法结构或实现特定的领域特定语言（DSL），而官方文档往往缺乏具体的实操细节。这份手册正是为了解决这一痛点，填补了从理论概念到工程落地之间的空白。\n\n它非常适合希望深入理解 TypeScript 编译器内部机制、需要定制编译流程的高级前端工程师及工具链开发者阅读。内容不仅涵盖了抽象语法树（AST）的基础概念和编译阶段解析，更提供了大量可运行的代码示例，详细演示了如何遍历节点、修改语法树、管理作用域以及处理复杂的类型检查逻辑。\n\n其独特的技术亮点在于对 Transformer API 的深度剖析，包括如何安全地替换节点、动态添加导入声明、重命名绑定以及在 Webpack 或 ttypescript 等主流构建工具中集成自定义转换器。此外，手册还分享了组合转换器、抛出友好语法错误等实用技巧，并介绍了专门的测试库，帮助开发者高效构建稳定可靠的编译插件。无论你是想扩展 TypeScript 的能力边界，还是致力于开发高效的构建工具，这份手册都是不可或缺的参考资源。","# TypeScript Transformer Handbook\n\nThis document covers how to write a [TypeScript](https:\u002F\u002Ftypescriptlang.org\u002F) [Transformer](https:\u002F\u002Fbasarat.gitbook.io\u002Ftypescript\u002Foverview\u002Fast).\n\n# Table of contents\n\n\u003C!-- toc -->\n\n- [Introduction](#introduction)\n  - [Running examples](#running-examples)\n- [The basics](#the-basics)\n  - [What is a abstract syntax tree (AST)](#what-is-a-abstract-syntax-tree-ast)\n  - [Stages](#stages)\n    - [A Program according to TypeScript](#a-program-according-to-typescript)\n    - [Parser](#parser)\n    - [Scanner](#scanner)\n    - [Binder](#binder)\n    - [Transforms](#transforms)\n    - [Emitting](#emitting)\n  - [Traversal](#traversal)\n    - [`visitNode()`](#visitnode)\n    - [`visitEachChild()`](#visiteachchild)\n    - [`visitor`](#visitor)\n    - [`context`](#context)\n  - [Scopes](#scopes)\n    - [Bindings](#bindings)\n- [Transformer API](#transformer-api)\n  - [Visiting](#visiting)\n  - [Nodes](#nodes)\n  - [`context`](#context-1)\n  - [`program`](#program)\n  - [`typeChecker`](#typechecker)\n- [Writing your first transformer](#writing-your-first-transformer)\n- [Types of transformers](#types-of-transformers)\n  - [Factory](#factory)\n  - [Config](#config)\n  - [Program](#program)\n- [Consuming transformers](#consuming-transformers)\n  - [`ttypescript`](#ttypescript)\n  - [`webpack`](#webpack)\n  - [`parcel`](#parcel)\n- [Transformation operations](#transformation-operations)\n  - [Visiting](#visiting-1)\n    - [Checking a node is a certain type](#checking-a-node-is-a-certain-type)\n    - [Check if two identifiers refer to the same symbol](#check-if-two-identifiers-refer-to-the-same-symbol)\n    - [Find a specific parent](#find-a-specific-parent)\n    - [Stopping traversal](#stopping-traversal)\n  - [Manipulation](#manipulation)\n    - [Updating a node](#updating-a-node)\n    - [Replacing a node](#replacing-a-node)\n    - [Replacing a node with multiple nodes](#replacing-a-node-with-multiple-nodes)\n    - [Inserting a sibling node](#inserting-a-sibling-node)\n    - [Removing a node](#removing-a-node)\n    - [Adding new import declarations](#adding-new-import-declarations)\n  - [Scope](#scope)\n    - [Pushing a variable declaration to the top of its scope](#pushing-a-variable-declaration-to-the-top-of-its-scope)\n    - [Pushing a variable declaration to a parent scope](#pushing-a-variable-declaration-to-a-parent-scope)\n    - [Checking if a local variable is referenced](#checking-if-a-local-variable-is-referenced)\n    - [Defining a unique variable](#defining-a-unique-variable)\n    - [Rename a binding and its references](#rename-a-binding-and-its-references)\n  - [Finding](#finding)\n    - [Get line number and column](#get-line-number-and-column)\n  - [Advanced](#advanced)\n    - [Evaluating expressions](#evaluating-expressions)\n    - [Following module imports](#following-module-imports)\n    - [Following node module imports](#following-node-module-imports)\n    - [Transforming jsx](#transforming-jsx)\n    - [Determining the file pragma](#determining-the-file-pragma)\n    - [Resetting the file pragma](#resetting-the-file-pragma)\n- [Tips & tricks](#tips--tricks)\n  - [Composing transformers](#composing-transformers)\n  - [Throwing a syntax error to ease the developer experience](#throwing-a-syntax-error-to-ease-the-developer-experience)\n- [Testing](#testing)\n  - [`ts-transformer-testing-library`](#ts-transformer-testing-library)\n- [Known bugs](#known-bugs)\n  - [EmitResolver cannot handle `JsxOpeningLikeElement` and `JsxOpeningFragment` that didn't originate from the parse tree](#emitresolver-cannot-handle-jsxopeninglikeelement-and-jsxopeningfragment-that-didnt-originate-from-the-parse-tree)\n  - [`getMutableClone(node)` blows up when used with `ts-loader`](#getmutableclonenode-blows-up-when-used-with-ts-loader)\n\n\u003C!-- tocstop -->\n\n# Introduction\n\nTypeScript is a typed superset of Javascript that compiles to plain Javascript.\nTypeScript supports the ability for consumers to _transform_ code from one form to another,\nsimilar to how [Babel](https:\u002F\u002Fbabeljs.io\u002F) does it with _plugins_.\n\n> Follow me [@itsmadou](https:\u002F\u002Ftwitter.com\u002Fitsmadou) for updates and general discourse\n\n## Running examples\n\nThere are multiple examples ready for you to use through this handbook.\nWhen you want to take the dive make sure to:\n\n1. clone the repo\n2. install deps with `yarn`\n3. build the example you want `yarn build example_name`\n\n# The basics\n\nA transformer when boiled down is essentially a function that takes and returns some piece of code,\nfor example:\n\n```js\nconst Transformer = code => code;\n```\n\nThe difference though is that instead of `code` being of type `string` -\nit is actually in the form of an abstract syntax tree (AST),\ndescribed below.\nWith it we can do powerful things like updating,\nreplacing,\nadding,\n& deleting `node`s.\n\n## What is a abstract syntax tree (AST)\n\nAbstract Syntax Trees,\nor ASTs,\nare a data structure that describes the code that has been parsed.\nWhen working with ASTs in TypeScript I'd strongly recommend using an AST explorer -\nsuch as [ts-ast-viewer.com](https:\u002F\u002Fts-ast-viewer.com).\n\nUsing such a tool we can see that the following code:\n\n```js\nfunction hello() {\n  console.log('world');\n}\n```\n\nIn its AST representation looks like this:\n\n```\n-> SourceFile\n  -> FunctionDeclaration\n      - Identifier\n  -> Block\n    -> ExpressionStatement\n      -> CallExpression\n        -> PropertyAccessExpression\n            - Identifier\n            - Identifier\n          - StringLiteral\n  - EndOfFileToken\n```\n\nFor a more detailed look check out the [AST yourself](https:\u002F\u002Fts-ast-viewer.com\u002F#code\u002FGYVwdgxgLglg9mABACwKYBt10QCgJSIDeAUImYhAgM5zqoB0WA5jgOQDucATugCat4A3MQC+QA)!\nYou can also see the code can be used to generate the same AST in the bottom left panel,\nand the selected node metadata in the right panel.\nSuper useful!\n\nWhen looking at the metadata you'll notice they all have a similar structure (some properties have been omitted):\n\n```js\n{\n  kind: 307, \u002F\u002F (SyntaxKind.SourceFile)\n  pos: 0,\n  end: 47,\n  statements: [{...}],\n}\n```\n\n```js\n{\n  kind: 262, \u002F\u002F (SyntaxKind.FunctionDeclaration)\n  pos: 0,\n  end: 47,\n  name: {...},\n  body: {...},\n}\n```\n\n```js\n{\n  kind: 244, \u002F\u002F (SyntaxKind.ExpressionStatement)\n  pos: 19,\n  end: 45,\n  expression: {...}\n}\n```\n\n> `SyntaxKind` is a TypeScript enum which describes the kind of node.\n> For [more information have a read of Basarat's AST tip](https:\u002F\u002Fbasarat.gitbook.io\u002Ftypescript\u002Foverview\u002Fast\u002Fast-tip-syntaxkind).\n\nAnd so on.\nEach of these describe a `Node`.\nASTs can be made from one to many -\nand together they describe the syntax of a program that can be used for static analysis.\n\nEvery node has a `kind` property which describes what kind of node it is,\nas well as `pos` and `end` which describe where in the source they are.\nWe will talk about how to narrow the node to a specific type of node later in the handbook.\n\n## Stages\n\nVery similar to Babel -\nTypeScript however has five stages,\n**parser**,\n_binder_,\n_checker_,\n**transform**,\n**emitting**.\n\nTwo steps are exclusive to TypeScript,\n_binder_ and _checker_.\nWe are going to gloss over _checker_ as it relates to TypeScripts type checking specifics.\n\n> For a more in-depth understanding of the TypeScript compiler internals have a read of [Basarat's handbook](https:\u002F\u002Fbasarat.gitbook.io\u002Ftypescript\u002F).\n\n### A Program according to TypeScript\n\nBefore we continue we need to quickly clarify exactly _what_ a `Program` is according to TypeScript.\nA `Program` is a collection of one or more entrypoint source files which consume one or more modules.\nThe _entire_ collection is then used during each of the stages.\n\nThis is in contrast to how Babel processes files -\nwhere Babel does file in file out,\nTypeScript does _project_ in,\nproject out.\nThis is why enums don't work when parsing TypeScript with Babel for example,\nit just doesn't have all the information available.\n\n### Parser\n\nThe TypeScript parser actually has two parts,\nthe `scanner`,\nand then the `parser`.\nThis step will convert source code into an AST.\n\n```\nSourceCode ~~ scanner ~~> Token Stream ~~ parser ~~> AST\n```\n\nThe parser takes source code and tries to convert it into an in-memory AST representation which you can work with in the compiler. Also: see [Parser](https:\u002F\u002Fbasarat.gitbooks.io\u002Ftypescript\u002Fcontent\u002Fdocs\u002Fcompiler\u002Fparser.html).\n\n### Scanner\n\nThe scanner is used by the parser to convert a string into tokens in a linear fashion,\nthen it's up to a parser to tree-ify them.\nAlso: see [Scanner](https:\u002F\u002Fbasarat.gitbooks.io\u002Ftypescript\u002Fdocs\u002Fcompiler\u002Fscanner.html).\n\n### Binder\n\nCreates a symbol map and uses the AST to provide the type system which is important to link references and to be able to know the nodes of imports and exports.\nAlso: see [Binder](https:\u002F\u002Fbasarat.gitbooks.io\u002Ftypescript\u002Fdocs\u002Fcompiler\u002Fbinder.html).\n\n### Transforms\n\nThis is the step we're all here for.\nIt allows us,\nthe developer,\nto change the code in any way we see fit.\nPerformance optimizations,\ncompile time behavior,\nreally anything we can imagine.\n\nThere are three stages of `transform` we care about:\n\n- `before` - which run transformers before the TypeScript ones (code has not been compiled)\n- `after` - which run transformers _after_ the TypeScript ones (code has been compiled)\n- `afterDeclarations` - which run transformers _after_ the **declaration** step (you can transform type defs here)\n\nGenerally the 90% case will see us always writing transformers for the `before` stage,\nbut if you need to do some post-compilation transformation,\nor modify types,\nyou'll end up wanting to use `after` and `afterDeclarations`.\n\n> **Tip** - Type checking _should_ not happen after transforming.\n> If it does it's more than likely a bug -\n> file an issue!\n\n### Emitting\n\nThis stage happens last and is responsible for _emitting_ the final code somewhere.\nGenerally this is usually to the file system -\nbut it could also be in memory.\n\n## Traversal\n\nWhen wanting to modify the AST in any way you need to traverse the tree -\nrecursively.\nIn more concrete terms we want to _visit each node_,\nand then return either the same,\nan updated,\nor a completely new node.\n\nIf we take the previous example AST in JSON format (with some values omitted):\n\n```js\n{\n  kind: 307, \u002F\u002F (SyntaxKind.SourceFile)\n  statements: [{\n    kind: 262, \u002F\u002F (SyntaxKind.FunctionDeclaration)\n    name: {\n      kind: 80 \u002F\u002F (SyntaxKind.Identifier)\n      escapedText: \"hello\"\n    },\n    body: {\n      kind: 241, \u002F\u002F (SyntaxKind.Block)\n      statements: [{\n        kind: 244, \u002F\u002F (SyntaxKind.ExpressionStatement)\n        expression: {\n          kind: 213, \u002F\u002F (SyntaxKind.CallExpression)\n          expression: {\n            kind: 211, \u002F\u002F (SyntaxKind.PropertyAccessExpression)\n            name: {\n              kind: 80 \u002F\u002F (SyntaxKind.Identifier)\n              escapedText: \"log\",\n            },\n            expression: {\n              kind: 80, \u002F\u002F (SyntaxKind.Identifier)\n              escapedText: \"console\",\n            }\n          }\n        },\n        arguments: [{\n          kind: 11, \u002F\u002F (SyntaxKind.StringLiteral)\n          text: \"world\",\n        }]\n      }]\n    }\n  }]\n}\n```\n\nIf we were to traverse it we would start at the `SourceFile` and then work through each node.\nYou might think you could meticulously traverse it yourself,\nlike `source.statements[0].name` etc,\nbut you'll find it won't scale and is prone to breaking very easily -\nso use it wisely.\n\nIdeally for the 90% case you'll want to use the built in methods to traverse the AST.\nTypeScript gives us two primary methods for doing this:\n\n### `visitNode()`\n\nGenerally you'll only pass this the initial `SourceFile` node.\nWe'll go into what the `visitor` function is soon.\n\n```ts\nimport * as ts from 'typescript';\n\nts.visitNode(sourceFile, visitor, test);\n```\n\n### `visitEachChild()`\n\nThis is a special function that uses `visitNode` internally.\nIt will handle traversing down to the inner most node -\nand it knows how to do it without you having the think about it.\nWe'll go into what the `context` object is soon.\n\n```ts\nimport * as ts from 'typescript';\n\nts.visitEachChild(node, visitor, context);\n```\n\n### `visitor`\n\nThe [`visitor` pattern](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FVisitor_pattern) is something you'll be using in every Transformer you write,\nluckily for us TypeScript handles it so we need to only supply a callback function.\nThe simplest function we could write might look something like this:\n\n```ts\nimport * as ts from 'typescript';\n\nconst transformer = sourceFile => {\n  const visitor = (node: ts.Node): ts.Node => {\n    console.log(node.kind, `\\t# ts.SyntaxKind.${ts.SyntaxKind[node.kind]}`);\n    return ts.visitEachChild(node, visitor, context);\n  };\n\n  return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n};\n```\n\n> **Note** - You'll see that we're _returning_ each node.\n> This is required!\n> If we didn't you'd see some funky errors.\n\nIf we applied this to the code example used before we would see this logged in our console (comments added afterwords):\n\n```sh\n307 \t# ts.SyntaxKind.SourceFile\n262 \t# ts.SyntaxKind.FunctionDeclaration\n80  \t# ts.SyntaxKind.Identifier\n241 \t# ts.SyntaxKind.Block\n244 \t# ts.SyntaxKind.ExpressionStatement\n213 \t# ts.SyntaxKind.CallExpression\n211 \t# ts.SyntaxKind.PropertyAccessExpression\n80  \t# ts.SyntaxKind.Identifier\n80  \t# ts.SyntaxKind.Identifier\n11  \t# ts.SyntaxKind.StringLiteral\n```\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Flog-every-node](\u002Fexample-transformers\u002Flog-every-node) - if wanting to run locally you can run it via `yarn build log-every-node`.\n\nIt goes as deep as possible entering each node,\nexiting when it bottoms out,\nand then entering other child nodes that it comes to.\n\n### `context`\n\nEvery transformer will receive the transformation `context`.\nThis context is used both for `visitEachChild`,\nas well as doing some useful things like getting a hold of what the current TypeScript configuration is.\nWe'll see our first look at a simple TypeScript transformer soon.\n\n## Scopes\n\n> Most of this content is taken directly from the [Babel Handbook](https:\u002F\u002Fgithub.com\u002Fjamiebuilds\u002Fbabel-handbook\u002Fblob\u002Fmaster\u002Ftranslations\u002Fen\u002Fplugin-handbook.md#scopes) as the same principles apply.\n\nNext let's introduce the concept of a [scope](\u003Chttps:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FScope_(computer_science)>).\nJavascript has lexical scoping ([closures](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FJavaScript\u002FClosures)),\nwhich is a tree structure where blocks create new scope.\n\n```js\n\u002F\u002F global scope\n\nfunction scopeOne() {\n  \u002F\u002F scope 1\n\n  function scopeTwo() {\n    \u002F\u002F scope 2\n  }\n}\n```\n\nWhenever you create a reference in Javascript,\nwhether that be by a variable,\nfunction,\nclass,\nparam,\nimport,\nlabel,\netc.,\nit belongs to the current scope.\n\n```js\nvar global = 'I am in the global scope';\n\nfunction scopeOne() {\n  var one = 'I am in the scope created by `scopeOne()`';\n\n  function scopeTwo() {\n    var two = 'I am in the scope created by `scopeTwo()`';\n  }\n}\n```\n\nCode within a deeper scope may use a reference from a higher scope.\n\n```js\nfunction scopeOne() {\n  var one = 'I am in the scope created by `scopeOne()`';\n\n  function scopeTwo() {\n    one = 'I am updating the reference in `scopeOne` inside `scopeTwo`';\n  }\n}\n```\n\nA lower scope might also create a reference of the same name without modifying it.\n\n```js\nfunction scopeOne() {\n  var one = 'I am in the scope created by `scopeOne()`';\n\n  function scopeTwo() {\n    var one = 'I am creating a new `one` but leaving reference in `scopeOne()` alone.';\n  }\n}\n```\n\nWhen writing a transform we want to be wary of scope.\nWe need to make sure we don't break existing code while modifying different parts of it.\n\nWe may want to add new references and make sure they don't collide with existing ones.\nOr maybe we just want to find where a variable is referenced.\nWe want to be able to track these references within a given scope.\n\n### Bindings\n\nReferences all belong to a particular scope;\nthis relationship is known as a binding.\n\n```js\nfunction scopeOnce() {\n  var ref = 'This is a binding';\n\n  ref; \u002F\u002F This is a reference to a binding\n\n  function scopeTwo() {\n    ref; \u002F\u002F This is a reference to a binding from a lower scope\n  }\n}\n```\n\n# Transformer API\n\nWhen writing your transformer you'll want to write it using TypeScript.\nYou'll be using the [`typescript`](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Ftypescript) package to do most of the heavy lifting.\nIt is used for everything,\nunlike Babel which has separate small packages.\n\nFirst,\nlet's install it.\n\n```sh\nnpm i typescript --save\n```\n\nAnd then let's import it:\n\n```ts\nimport * as ts from 'typescript';\n```\n\n> **Tip** - I _strongly recommend_ using intellisense in VSCode to interrogate the API,\n> it's super useful!\n\n## Visiting\n\nThese methods are useful for visiting nodes -\nwe've briefly gone over a few of them above.\n\n- `ts.visitNode(node, visitor, test)` - useful for visiting the root node, generally the `SourceFile`\n- `ts.visitEachChild(node, visitor, context)` - useful for visiting each child of a node\n- `ts.isXyz(node)` - useful for narrowing the type of a `node`, an example of this is `ts.isVariableDeclaration(node)`\n\n## Nodes\n\nThese methods are useful for modifying a `node` in some form.\n\n- `ts.factory.createXyz(...)` - useful for creating a new node (to then return), an example of this is `ts.factory.createIdentifier('world')`\n- `ts.factory.updateXyz(node, ...)` - useful for updating a node (to then return), an example of this is `ts.factory.updateVariableDeclaration()`\n- `ts.factory.updateSourceFile(sourceFile, ...)` - useful for updating a source file to then return\n- `ts.setOriginalNode(newNode, originalNode)` - useful for setting a nodes original node\n- `ts.setXyz(...)` - sets things\n- `ts.addXyz(...)` - adds things\n\n## `context`\n\nCovered above,\nthis is supplied to every transformer and has some handy methods available (this is not an exhaustive list,\njust the stuff we care about):\n\n- `getCompilerOptions()` - Gets the compiler options supplied to the transformer\n- `hoistFunctionDeclaration(node)` - Hoists a function declaration to the top of the containing scope\n- `hoistVariableDeclaration(node)` - Hoists a variable declaration to the tope of the containing scope\n\n## `program`\n\nThis is a special property that is available when writing a Program transformer.\nWe will cover this kind of transformer in [Types of transformers](#types-of-transformers).\nIt contains metadata about the _entire program_,\nsuch as (this is not an exhaustive list,\njust the stuff we care about):\n\n- `getRootFileNames()` - get an array of file names in the project\n- `getSourceFiles()` - gets all `SourceFile`s in the project\n- `getCompilerOptions()` - compiler options from the `tsconfig.json`, command line, or other (can also get it from `context`)\n- `getSourceFile(fileName: string)` - gets a `SourceFile` using its `fileName`\n- `getSourceFileByPath(path: Path)` - gets a `SourceFile` using its `path`\n- `getCurrentDirectory()` - gets the current directory string\n- `getTypeChecker()` - gets ahold of the type checker, useful when doing things with [Symbols](https:\u002F\u002Fbasarat.gitbooks.io\u002Ftypescript\u002Fcontent\u002Fdocs\u002Fcompiler\u002Fbinder.html)\n\n## `typeChecker`\n\nThis is the result of calling `program.getTypeChecker()`.\nIt has a lot of interesting things on in that we'll be interested in when writing transformers.\n\n- `getSymbolAtLocation(node)` - useful for getting the symbol of a node\n- `getExportsOfModule(symbol)` - will return the exports of a module symbol\n\n# Writing your first transformer\n\nIt's the part we've all be waiting for!\nLet's write out first transformer.\n\nFirst let's import `typescript`.\n\n```ts\nimport * as ts from 'typescript';\n```\n\nIt's going to contain everything that we could use when writing a transformer.\n\nNext let's create a default export that is going to be our transformer,\nour initial transformer we be a transformer factory (because this gives us access to `context`) -\nwe'll go into the other kinds of transformers later.\n\n```ts\nimport * as ts from 'typescript';\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n    \u002F\u002F transformation code here\n  };\n};\n\nexport default transformer;\n```\n\nBecause we're using TypeScript to write out transformer -\nwe get all the type safety and more importantly intellisense!\nIf you're up to here you'll notice TypeScript complaining that we aren't returning a `SourceFile` -\nlet's fix that.\n\n```diff\nimport * as ts from \"typescript\";\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n    \u002F\u002F transformation code here\n+    return sourceFile;\n  };\n};\n\nexport default transformer;\n```\n\nSweet we fixed the type error!\n\nFor our first transformer we'll take a hint from the [Babel Handbook](https:\u002F\u002Fgithub.com\u002Fjamiebuilds\u002Fbabel-handbook\u002Fblob\u002Fmaster\u002Ftranslations\u002Fen\u002Fplugin-handbook.md#writing-your-first-babel-plugin) and rename some identifiers.\n\nHere's our source code:\n\n```ts\nbabel === plugins;\n```\n\nLet's write a visitor function,\nremember that a visitor function should take a `node` of a particular type (here a `SourceFile`),\nand then return a `node` of the same type. Note that the `test` parameter of `visitNode` can be used\nto ensure that nodes of a particular type are returned.\n\n```diff\nimport * as ts from 'typescript';\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n+    const visitor = (node: ts.Node): ts.Node => {\n+      return node;\n+    };\n+\n+    return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n-\n-    return sourceFile;\n  };\n};\n\nexport default transformer;\n```\n\nOkay that will visit the `SourceFile`...\nand then just immediately return it.\nThat's a bit useless -\nlet's make sure we visit every node!\n\n```diff\nimport * as ts from 'typescript';\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n    const visitor = (node: ts.Node): ts.Node => {\n-      return node;\n+      return ts.visitEachChild(node, visitor, context);\n    };\n\n    return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n  };\n};\n\nexport default transformer;\n```\n\nNow let's find identifiers so we can rename them:\n\n```diff\nimport * as ts from 'typescript';\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n    const visitor = (node: ts.Node): ts.Node => {\n+      if (ts.isIdentifier(node)) {\n+        \u002F\u002F transform here\n+      }\n\n      return ts.visitEachChild(node, visitor, context);\n    };\n\n    return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n  };\n};\n\nexport default transformer;\n```\n\nAnd then let's target the specific identifiers we're interested in:\n\n```diff\nimport * as ts from 'typescript';\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n    const visitor = (node: ts.Node): ts.Node => {\n      if (ts.isIdentifier(node)) {\n+        switch (node.escapedText) {\n+          case 'babel':\n+            \u002F\u002F rename babel\n+\n+          case 'plugins':\n+            \u002F\u002F rename plugins\n+        }\n      }\n\n      return ts.visitEachChild(node, visitor, context);\n    };\n\n    return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n  };\n};\n\nexport default transformer;\n```\n\nAnd then let's return new nodes that have been renamed!\n\n```diff\nimport * as ts from 'typescript';\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n    const visitor = (node: ts.Node): ts.Node => {\n      if (ts.isIdentifier(node)) {\n        switch (node.escapedText) {\n          case 'babel':\n+            return ts.factory.createIdentifier('typescript');\n\n          case 'plugins':\n+            return ts.factory.createIdentifier('transformers');\n        }\n      }\n\n      return ts.visitEachChild(node, visitor, context);\n    };\n\n    return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n  };\n};\n\nexport default transformer;\n```\n\nSweet!\nWhen ran over our source code we get this output:\n\n```ts\ntypescript === transformers;\n```\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Fmy-first-transformer](\u002Fexample-transformers\u002Fmy-first-transformer) - if wanting to run locally you can run it via `yarn build my-first-transformer`.\n\n# Types of transformers\n\nAll transformers end up returning the `TransformerFactory` type signature.\nThese types of transformers are taken from [`ttypescript`](https:\u002F\u002Fgithub.com\u002Fcevek\u002Fttypescript).\n\n## Factory\n\nAlso known as `raw`,\nthis is the same as the one used in writing your first transformer.\n\n```ts\n\u002F\u002F ts.TransformerFactory\n(context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile;\n```\n\n## Config\n\nWhen your transformer needs config that can be controlled by consumers.\n\n```ts\n(config?: YourPluginConfigInterface) => ts.TransformerFactory;\n```\n\n## Program\n\nWhen needing access to the `program` object this is the signature you should use,\nit should return a `TransformerFactory`.\nIt also has configuration available as the second object,\nsupplied by consumers.\n\n```ts\n(program: ts.Program, config?: YourPluginConfigInterface) => ts.TransformerFactory;\n```\n\n# Consuming transformers\n\nAmusingly TypeScript has no official support for consuming transformers via `tsconfig.json`.\nThere is a [GitHub issue](https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FTypeScript\u002Fissues\u002F14419) dedicated to talking about introducing something for it.\nRegardless you can consume transformers it's just a little round-about.\n\n## [`ts-patch`](https:\u002F\u002Fgithub.com\u002Fnonara\u002Fts-patch)\n\n> **This is the recommended approach**!\n> Hopefully in the future this can be officially supported in `typescript`.\n\nEssentially a wrapper over the top of the `tsc` CLI -\nthis gives first class support to transformers via the `tsconfig.json`.\nIt has `typescript` listed as a peer dependency so the theory is it isn't too brittle.\n\nInstall:\n\n```sh\nnpm i ts-patch -D\n```\n\nAdd your transformer into the compiler options:\n\n```json\n{\n  \"compilerOptions\": {\n    \"plugins\": [{ \"transform\": \"my-first-transformer\" }]\n  }\n}\n```\n\nRun `tspc`:\n\n```sh\ntspc\n```\n\n`ts-patch` supports `tsc` CLI,\nWebpack,\nRollup,\nJest,\n& VSCode.\nEverything we would want to use TBH.\n\n# Transformation operations\n\n## Visiting\n\n### Checking a node is a certain type\n\nThere is a wide variety of helper methods that can assert what type a node is.\nWhen they return true they will _narrow_ the type of the `node`,\npotentially giving you extra properties & methods based on the type.\n\n> **Tip** - Abuse intellisense to interrogate the `ts` import for methods you can use,\n> as well as [TypeScript AST Viewer](https:\u002F\u002Fts-ast-viewer.com\u002F) to know what type a node is.\n\n```ts\nimport * as ts from 'typescript';\n\nconst visitor = (node: ts.Node): ts.Node => {\n  if (ts.isJsxAttribute(node.parent)) {\n    \u002F\u002F node.parent is a jsx attribute\n    \u002F\u002F ...\n  }\n};\n```\n\n### Check if two identifiers refer to the same symbol\n\nIdentifiers are created by the parser and are always unique.\nSay, if you create a variable `foo` and use it in another line, it will create 2 separate identifiers with the same text `foo`.\n\nThen, the linker runs through these identifiers and connects the identifiers referring to the same variable with a common symbol (while considering scope and shadowing). Think of symbols as what we intuitively think as variables.\n\nSo, to check if two identifiers refer to the same symbol - just get the symbols related to the identifier and check if they are the same (by reference).\n\n**Short example** -\n\n```ts\nconst symbol1 = typeChecker.getSymbolAtLocation(node1);\nconst symbol2 = typeChecker.getSymbolAtLocation(node2);\n\nsymbol1 === symbol2; \u002F\u002F check by reference\n```\n\n**Full example** -\n\nThis will log all repeating symbols.\n\n```ts\nimport * as ts from 'typescript';\n\nconst transformerProgram = (program: ts.Program) => {\n  const typeChecker = program.getTypeChecker();\n\n  \u002F\u002F Create array of found symbols\n  const foundSymbols = new Array\u003Cts.Symbol>();\n\n  const transformerFactory: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n    return sourceFile => {\n      const visitor = (node: ts.Node): ts.Node => {\n        if (ts.isIdentifier(node)) {\n          const relatedSymbol = typeChecker.getSymbolAtLocation(node);\n\n          \u002F\u002F Check if array already contains same symbol - check by reference\n          if (foundSymbols.includes(relatedSymbol)) {\n            const foundIndex = foundSymbols.indexOf(relatedSymbol);\n            console.log(\n              `Found existing symbol at position = ${foundIndex} and name = \"${relatedSymbol.name}\"`\n            );\n          } else {\n            \u002F\u002F If not found, Add it to array\n            foundSymbols.push(relatedSymbol);\n\n            console.log(\n              `Found new symbol with name = \"${\n                relatedSymbol.name\n              }\". Added at position = ${foundSymbols.length - 1}`\n            );\n          }\n\n          return node;\n        }\n\n        return ts.visitEachChild(node, visitor, context);\n      };\n\n      return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n    };\n  };\n\n  return transformerFactory;\n};\n\nexport default transformerProgram;\n```\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Fmatch-identifier-by-symbol](\u002Fexample-transformers\u002Fmatch-identifier-by-symbol) - if wanting to run locally you can run it via `yarn build match-identifier-by-symbol`.\n\n### Find a specific parent\n\nWhile there doesn't exist an out of the box method you can basically roll your own.\nGiven a node:\n\n```ts\nconst findParent = (node: ts.Node, predicate: (node: ts.Node) => boolean) => {\n  if (!node.parent) {\n    return undefined;\n  }\n\n  if (predicate(node.parent)) {\n    return node.parent;\n  }\n\n  return findParent(node.parent, predicate);\n};\n\nconst visitor = (node: ts.Node): ts.Node => {\n  if (ts.isStringLiteral(node)) {\n    const parent = findParent(node, ts.isFunctionDeclaration);\n    if (parent) {\n      console.log('string literal has a function declaration parent');\n    }\n    return node;\n  }\n};\n```\n\nWill log to console `string literal has a function declaration parent` with the following source:\n\n```ts\nfunction hello() {\n  if (true) {\n    'world';\n  }\n}\n```\n\n- Be careful when traversing after replacing a node with another - `parent` may not be set.\n  If you need to traverse after transforming make sure to set `parent` on the node yourself.\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Ffind-parent](\u002Fexample-transformers\u002Ffind-parent) - if wanting to run locally you can run it via `yarn build find-parent`.\n\n### Stopping traversal\n\nIn the visitor function you can return early instead of continuing down children,\nso for example if we hit a node and we know we don't need to go any further:\n\n```ts\nconst visitor = (node: ts.Node): ts.Node => {\n  if (ts.isArrowFunction(node)) {\n    \u002F\u002F return early\n    return node;\n  }\n};\n```\n\n## Manipulation\n\n### Updating a node\n\n```ts\nif (ts.isVariableDeclaration(node)) {\n  return ts.updateVariableDeclaration(\n    node, \n    node.name, \n    undefined, \n    node.type, \n    ts.createStringLiteral('world')\n  );\n}\n```\n\n```diff\n-const hello = true;\n+const hello = \"updated-world\";\n```\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Fupdate-node](\u002Fexample-transformers\u002Fupdate-node) - if wanting to run locally you can run it via `yarn build update-node`.\n\n### Replacing a node\n\nMaybe instead of updating a node we want to completely change it.\nWe can do that by just returning... a completely new node!\n\n```ts\nif (ts.isFunctionDeclaration(node)) {\n  \u002F\u002F Will replace any function it finds with an arrow function.\n  return ts.factory.createVariableDeclarationList(\n    [\n      ts.factory.createVariableDeclaration(\n        ts.factory.createIdentifier(node.name.escapedText),\n        undefined,\n        ts.factory.createArrowFunction(\n          undefined,\n          undefined,\n          [],\n          undefined,\n          ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),\n          ts.factory.createBlock([], false)\n        )\n      ),\n    ],\n    ts.NodeFlags.Const\n  );\n}\n```\n\n```diff\n-function helloWorld() {}\n+const helloWorld = () => {};\n```\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Freplace-node](\u002Fexample-transformers\u002Freplace-node) - if wanting to run locally you can run it via `yarn build replace-node`.\n\n### Replacing a node with multiple nodes\n\nInterestingly, a visitor function can also return an array of nodes instead of just one node.\nThat means, even though it gets one node as input, it can return multiple nodes which replaces that input node.\n\n```ts\ntype Visitor\u003CTIn extends Node = Node, TOut extends Node | undefined = TIn | undefined> = \n  (node: TIn) => VisitResult\u003CTOut>;\ntype VisitResult\u003CT extends Node | undefined> = T | readonly Node[];\n```\n\nLet's just replace every expression statement with two copies of the same statement (duplicating it) -\n\n```ts\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n    const visitor = (node: ts.Node): ts.VisitResult\u003Cts.Node> => {\n      \u002F\u002F If it is a expression statement,\n      if (ts.isExpressionStatement(node)) {\n        \u002F\u002F Return it twice.\n        \u002F\u002F Effectively duplicating the statement\n        return [node, node];\n      }\n\n      return ts.visitEachChild(node, visitor, context);\n    };\n\n    return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n  };\n};\n```\n\nSo,\n\n```ts\nlet a = 1;\na = 2;\n```\n\nbecomes\n\n```js\nlet a = 1;\na = 2;\na = 2;\n```\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Freturn-multiple-node](\u002Fexample-transformers\u002Freturn-multiple-node) - if wanting to run locally you can run it via `yarn build return-multiple-node`.\n\nThe declaration statement (first line) is ignored as it's not a `ExpressionStatement`.\n\n_Note_ - Make sure that what you are trying to do actually makes sense in the AST. For ex., returning two expressions instead of one is often just invalid.\n\nSay there is an assignment expression (BinaryExpression with with EqualToken operator), `a = b = 2`. Now returning two nodes instead of `b = 2` expression is invalid (because right hand side can not be multiple nodes). So, TS will throw an error - `Debug Failure. False expression: Too many nodes written to output.`\n\n### Inserting a sibling node\n\nThis is effectively same as the [previous section](#replacing-a-node-with-multiple-nodes). Just return a array of nodes including itself and other sibling nodes.\n\n### Removing a node\n\nWhat if you don't want a specific node anymore?\nReturn an `undefined`!\n\n```ts\nif (ts.isImportDeclaration(node)) {\n  \u002F\u002F Will remove all import declarations\n  return undefined;\n}\n```\n\n```diff\nimport lodash from 'lodash';\n-import lodash from 'lodash';\n```\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Fremove-node](\u002Fexample-transformers\u002Fremove-node) - if wanting to run locally you can run it via `yarn build remove-node`.\n\n### Adding new import declarations\n\nSometimes your transformation will need some runtime part,\nfor that you can add your own import declaration.\n\n```ts\nts.factory.updateSourceFile(sourceFile, [\n  ts.factory.createImportDeclaration(\n    \u002F* modifiers *\u002F undefined,\n    ts.factory.createImportClause(\n      false,\n      ts.factory.createIdentifier('DefaultImport'),\n      ts.factory.createNamedImports([\n        ts.factory.createImportSpecifier(\n          false, \n          undefined, \n          ts.factory.createIdentifier('namedImport')\n        ),\n      ])\n    ),\n    ts.factory.createStringLiteral('package')\n  ),\n  \u002F\u002F Ensures the rest of the source files statements are still defined.\n  ...sourceFile.statements,\n]);\n```\n\n```diff\n+import DefaultImport, { namedImport } from \"package\";\n```\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Fadd-import-declaration](\u002Fexample-transformers\u002Fadd-import-declaration) - if wanting to run locally you can run it via `yarn build add-import-declaration`.\n\n## Scope\n\n### Pushing a variable declaration to the top of its scope\n\nSometimes you may want to push a `VariableDeclaration` so you can assign to it.\nRemember that this only hoists the variable -\nthe assignment will still be where it was in the source.\n\n```ts\nif (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {\n  context.hoistVariableDeclaration(node.name);\n  return node;\n}\n```\n\n```diff\nfunction functionOne() {\n+  var innerOne;\n+  var innerTwo;\n  const innerOne = true;\n  const innerTwo = true;\n}\n```\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Fhoist-variable-declaration](\u002Fexample-transformers\u002Fhoist-variable-declaration) - if wanting to run locally you can run it via `yarn build hoist-variable-declaration`.\n\nYou can also do this with function declarations:\n\n```ts\nif (ts.isFunctionDeclaration(node)) {\n  context.hoistFunctionDeclaration(node);\n  return node;\n}\n```\n\n```diff\n+function functionOne() {\n+    console.log('hello, world!');\n+}\nif (true) {\n  function functionOne() {\n    console.log('hello, world!');\n  }\n}\n```\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Fhoist-function-declaration](\u002Fexample-transformers\u002Fhoist-function-declaration) - if wanting to run locally you can run it via `yarn build hoist-function-declaration`.\n\n### Pushing a variable declaration to a parent scope\n\n> **TODO** - Is this possible?\n\n### Checking if a local variable is referenced\n\n> **TODO** - Is this possible?\n\n### Defining a unique variable\n\nSometimes you want to add a new variable that has a unique name within its scope,\nluckily it's possible without needing to go through any hoops.\n\n```ts\nif (ts.isVariableDeclarationList(node)) {\n  return ts.factory.updateVariableDeclarationList(node, [\n    ...node.declarations,\n    ts.factory.createVariableDeclaration(\n      ts.factory.createUniqueName('hello'),\n      undefined \u002F* exclamation token *\u002F,\n      undefined \u002F* type *\u002F,\n      ts.factory.createStringLiteral('world')\n    ),\n  ]);\n}\n\nreturn ts.visitEachChild(node, visitor, context);\n```\n\n```diff\n-const hello = 'world';\n+const hello = 'world', hello_1 = \"world\";\n```\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Fcreate-unique-name](\u002Fexample-transformers\u002Fcreate-unique-name) - if wanting to run locally you can run it via `yarn build create-unique-name`.\n\n### Rename a binding and its references\n\n> **TODO** - Is this possible in a concise way?\n\n## Finding\n\n### Get line number and column\n\n```\nsourceFile.getLineAndCharacterOfPosition(node.getStart());\n```\n\n## Advanced\n\n### Evaluating expressions\n\n> **TODO** - Is this possible?\n\n### Following module imports\n\nIt's possible!\n\n```ts\n\u002F\u002F We need to use a Program transformer to get ahold of the program object.\nconst transformerProgram = (program: ts.Program) => {\n  const transformerFactory: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n    return sourceFile => {\n      const visitor = (node: ts.Node): ts.Node => {\n        if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {\n          const typeChecker = program.getTypeChecker();\n          const importSymbol = typeChecker.getSymbolAtLocation(node.moduleSpecifier)!;\n          const exportSymbols = typeChecker.getExportsOfModule(importSymbol);\n\n          exportSymbols.forEach(symbol =>\n            console.log(\n              `found \"${\n                symbol.escapedName\n              }\" export with value \"${symbol.valueDeclaration!.getText()}\"`\n            )\n          );\n\n          return node;\n        }\n\n        return ts.visitEachChild(node, visitor, context);\n      };\n\n      return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n    };\n  };\n\n  return transformerFactory;\n};\n```\n\nWhich will log this to the console:\n\n```\nfound \"hello\" export with value \"hello = 'world'\"\nfound \"default\" export with value \"export default 'hello';\"\n```\n\nYou can also traverse the imported node as well using `ts.visitChild` and the like.\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Ffollow-imports](\u002Fexample-transformers\u002Ffollow-imports) - if wanting to run locally you can run it via `yarn build follow-imports`.\n\n### Following node module imports\n\nLike following TypeScript imports for the code that you own,\nsometimes we may want to also interrogate the code inside a module we're importing.\n\nUsing the same code above except running on a `node_modules` import we get this logged to the console:\n\n```\nfound \"mixin\" export with value:\nexport declare function mixin(): {\n  color: string;\n};\"\nfound \"constMixin\" export with value:\nexport declare function constMixin(): {\n  color: 'blue';\n};\"\n```\n\nHmm what - we're getting the type def AST instead of source code...\nLame!\n\nSo it turns out it's a little harder for us to get this working (at least out of the box).\nIt turns out we have two options :\n\n1. Turn on `allowJs` in the tsconfig and the **delete the type def**...\n   which will give us the source AST...\n   but we now won't have type defs...\n   So this isn't desirable.\n2. Create another TS program and do the dirty work ourselves\n\n**Spoiler:** _We're going with option 2_.\nIt's more resilient and will work when type checking is turned off -\nwhich is also how we'll follow TypeScript imports in that scenario!\n\n```ts\nconst visitor = (node: ts.Node): ts.Node => {\n  if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {\n    \u002F\u002F Find the import location in the file system using require.resolve\n    const pkgEntry = require.resolve(`${node.moduleSpecifier.text}`);\n\n    \u002F\u002F Create another program\n    const innerProgram = ts.createProgram([pkgEntry], {\n      \u002F\u002F Important to set this to true!\n      allowJs: true,\n    });\n\n    console.log(innerProgram.getSourceFile(pkgEntry)?.getText());\n\n    return node;\n  }\n\n  return ts.visitEachChild(node, visitor, context);\n};\n```\n\nWhich will log this to the console:\n\n```\nexport function mixin() {\n  return { color: 'red' };\n}\n\nexport function constMixin() {\n  return { color: 'blue' }\n}\n```\n\nAwesome!\nThe cool thing about this btw is that since we've made a _program_ we will get all of _its_ imports followed for free!\nHowever it'll have the same problem as above if they have type defs -\nso watch out if you need to jump through multiple imports -\nyou'll probably have to do something more clever.\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Ffollow-node-modules-imports](\u002Fexample-transformers\u002Ffollow-node-modules-imports) - if wanting to run locally you can run it via `yarn build follow-node-modules-imports`.\n\n### Transforming jsx\n\nTypeScript can also transform [JSX](https:\u002F\u002Freactjs.org\u002Fdocs\u002Fintroducing-jsx.html) -\nthere are a handful of helper methods to get started.\nAll previous methods of visiting and manipulation apply.\n\n- `ts.isJsxXyz(node)`\n- `ts.factory.updateJsxXyz(node, ...)`\n- `ts.factory.createJsxXyz(...)`\n\nInterrogate the typescript import for more details.\nThe primary point is you need to create valid JSX -\nhowever if you ensure the types are valid in your transformer it's very hard to get it wrong.\n\n### Determining the file pragma\n\nUseful when wanting to know what the file pragma is so you can do something in your transform.\nSay for example we wanted to know if a custom `jsx` pragma is being used:\n\n```ts\nconst transformer = sourceFile => {\n  const jsxPragma = (sourceFile as any).pragmas.get('jsx'); \u002F\u002F see below regarding the cast to `any`\n  if (jsxPragma) {\n    console.log(`a jsx pragma was found using the factory \"${jsxPragma.arguments.factory}\"`);\n  }\n\n  return sourceFile;\n};\n```\n\nThe source file below would cause `'a jsx pragma was found using the factory \"jsx\"'` to be logged to console.\n\n```ts\n\u002F** @jsx jsx *\u002F\n```\n\n> **Tip** - You can see the source for this at [\u002Fexample-transformers\u002Fpragma-check](\u002Fexample-transformers\u002Fpragma-check) - if wanting to run locally you can run it via `yarn build pragma-check`.\n\nCurrently as of 29\u002F12\u002F2019 `pragmas` is not on the typings for `sourceFile` -\nso you'll have to cast it to `any` to gain access to it.\n\n### Resetting the file pragma\n\nSometimes during transformation you might want to change the pragma _back_ to the default (in our case React).\nI've found success with the following code:\n\n```ts\nconst transformer = sourceFile => {\n  sourceFile.pragmas.clear();\n  delete sourceFile.localJsxFactory;\n};\n```\n\n# Tips & tricks\n\n## Composing transformers\n\nIf you're like me sometimes you want to split your big transformer up into small more maintainable pieces.\nWell luckily with a bit of coding elbow grease we can achieve this:\n\n```ts\nconst transformers = [...];\n\nfunction transformer(\n  program: ts.Program,\n): ts.TransformerFactory\u003Cts.SourceFile> {\n  return context => {\n    const initializedTransformers = transformers.map(transformer => transformer(program)(context));\n\n    return sourceFile => {\n      return initializedTransformers.reduce((source, transformer) => {\n        return transformer(source);\n      }, sourceFile);\n    };\n  };\n}\n```\n\n## Throwing a syntax error to ease the developer experience\n\n> **TODO** - Is this possible like it is in Babel?\n> Or we use a [language service plugin](https:\u002F\u002Fgithub.com\u002FMicrosoft\u002FTypeScript\u002Fwiki\u002FWriting-a-Language-Service-Plugin)?\n\n# Testing\n\nGenerally with transformers the the usefulness of unit tests is quite limited.\nI recommend writing integration tests to allow your tests to be super useful and resilient.\nThis boils down to:\n\n- **Write integration tests** over unit tests\n- Avoid snapshot tests - only do it if it makes sense - **the larger the snapshot the less useful it is**\n- Try to pick apart specific behavior for every test you write - and only **assert one thing per test**\n\nIf you want you can use the [TypeScript compiler API](https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FTypeScript\u002Fwiki\u002FUsing-the-Compiler-API#a-simple-transform-function) to setup your transformer for testing,\nbut I'd recommend using a library instead.\n\n## [`ts-transformer-testing-library`](https:\u002F\u002Fgithub.com\u002Fmarionebl\u002Fts-transformer-testing-library)\n\nThis library makes testing transformers easy.\nIt is made to be used in conjunction with a test runner such as [`jest`](https:\u002F\u002Fgithub.com\u002Ffacebook\u002Fjest).\nIt simplifies the setup of your transformer,\nbut still allows you to write your tests as you would for any other piece of software.\n\nHere's an example test using it:\n\n```ts\nimport { Transformer } from 'ts-transformer-testing-library';\nimport transformerFactory from '..\u002Findex';\nimport pkg from '..\u002F..\u002F..\u002F..\u002Fpackage.json';\n\nconst transformer = new Transformer()\n  .addTransformer(transformerFactory)\n  .addMock({ name: pkg.name, content: `export const jsx: any = () => null` })\n  .addMock({\n    name: 'react',\n    content: `export default {} as any; export const useState = {} as any;`,\n  })\n  .setFilePath('\u002Findex.tsx');\n\nit('should add react default import if it only has named imports', () => {\n  const actual = transformer.transform(`\n    \u002F** @jsx jsx *\u002F\n    import { useState } from 'react';\n    import { jsx } from '${pkg.name}';\n\n    \u003Cdiv css={{}}>hello world\u003C\u002Fdiv>\n  `);\n\n  \u002F\u002F We are also using `jest-extended` here to add extra matchers to the jest object.\n  expect(actual).toIncludeRepeated('import React, { useState } from \"react\"', 1);\n});\n```\n\n# Known bugs\n\n## EmitResolver cannot handle `JsxOpeningLikeElement` and `JsxOpeningFragment` that didn't originate from the parse tree\n\nIf you replace a node with a new jsx element like this:\n\n```tsx\nconst visitor = node => {\n  return ts.factory.createJsxFragment(\n    ts.factory.createJsxOpeningFragment(), \n    [], \n    ts.factory.createJsxJsxClosingFragment()\n  );\n};\n```\n\nIt will blow up if there are any surrounding `const` or `let` variables.\nA work around is to ensure the opening\u002Fclosing elements are passed into `ts.setOriginalNode`:\n\n```diff\nts.createJsxFragment(\n-  ts.createJsxOpeningFragment(),\n+  ts.setOriginalNode(ts.factory.createJsxOpeningFragment(), node),\n  [],\n-  ts.createJsxJsxClosingFragment()\n+  ts.setOriginalNode(ts.factory.createJsxJsxClosingFragment(), node)\n);\n```\n\nSee https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FTypeScript\u002Fissues\u002F35686 for more information.\n","# TypeScript 转换器手册\n\n本文档介绍了如何编写一个 [TypeScript](https:\u002F\u002Ftypescriptlang.org\u002F) 的 [转换器](https:\u002F\u002Fbasarat.gitbook.io\u002Ftypescript\u002Foverview\u002Fast)。\n\n# 目录\n\n\u003C!-- toc -->\n\n- [引言](#introduction)\n  - [运行示例](#running-examples)\n- [基础知识](#the-basics)\n  - [什么是抽象语法树 (AST)](#what-is-a-abstract-syntax-tree-ast)\n  - [阶段](#stages)\n    - [按照 TypeScript 的定义，一个程序](#a-program-according-to-typescript)\n    - [解析器](#parser)\n    - [词法分析器](#scanner)\n    - [绑定器](#binder)\n    - [转换](#transforms)\n    - [生成](#emitting)\n  - [遍历](#traversal)\n    - [`visitNode()`](#visitnode)\n    - [`visitEachChild()`](#visiteachchild)\n    - [`visitor`](#visitor)\n    - [`context`](#context)\n  - [作用域](#scopes)\n    - [绑定](#bindings)\n- [转换器 API](#transformer-api)\n  - [访问](#visiting)\n  - [节点](#nodes)\n  - [`context`](#context-1)\n  - [`program`](#program)\n  - [`typeChecker`](#typechecker)\n- [编写你的第一个转换器](#writing-your-first-transformer)\n- [转换器的类型](#types-of-transformers)\n  - [工厂](#factory)\n  - [配置](#config)\n  - [程序](#program)\n- [使用转换器](#consuming-transformers)\n  - [`ttypescript`](#ttypescript)\n  - [`webpack`](#webpack)\n  - [`parcel`](#parcel)\n- [转换操作](#transformation-operations)\n  - [访问](#visiting-1)\n    - [检查节点是否为特定类型](#checking-a-node-is-a-certain-type)\n    - [检查两个标识符是否引用同一个符号](#check-if-two-identifiers-refer-to-the-same-symbol)\n    - [查找特定父节点](#find-a-specific-parent)\n    - [停止遍历](#stopping-traversal)\n  - [操作](#manipulation)\n    - [更新节点](#updating-a-node)\n    - [替换节点](#replacing-a-node)\n    - [用多个节点替换一个节点](#replacing-a-node-with-multiple-nodes)\n    - [插入兄弟节点](#inserting-a-sibling-node)\n    - [删除节点](#removing-a-node)\n    - [添加新的 import 声明](#adding-new-import-declarations)\n  - [作用域](#scope)\n    - [将变量声明提升到其作用域的顶部](#pushing-a-variable-declaration-to-the-top-of-its-scope)\n    - [将变量声明提升到父作用域](#pushing-a-variable-declaration-to-a-parent-scope)\n    - [检查局部变量是否被引用](#checking-if-a-local-variable-is-referenced)\n    - [定义一个唯一变量](#defining-a-unique-variable)\n    - [重命名绑定及其引用](#rename-a-binding-and-its-references)\n  - [查找](#finding)\n    - [获取行号和列号](#get-line-number-and-column)\n  - [高级操作](#advanced)\n    - [求值表达式](#evaluating-expressions)\n    - [跟踪模块导入](#following-module-imports)\n    - [跟踪 Node.js 模块导入](#following-node-module-imports)\n    - [转换 JSX](#transforming-jsx)\n    - [确定文件 pragma](#determining-the-file-pragma)\n    - [重置文件 pragma](#resetting-the-file-pragma)\n- [提示与技巧](#tips--tricks)\n  - [组合转换器](#composing-transformers)\n  - [抛出语法错误以提升开发者体验](#throwing-a-syntax-error-to-ease-the-developer-experience)\n- [测试](#testing)\n  - [`ts-transformer-testing-library`](#ts-transformer-testing-library)\n- [已知问题](#known-bugs)\n  - [EmitResolver 无法处理并非源自解析树的 `JsxOpeningLikeElement` 和 `JsxOpeningFragment`](#emitresolver-cannot-handle-jsxopeninglikeelement-and-jsxopeningfragment-that-didnt-originate-from-the-parse-tree)\n  - [`getMutableClone(node)` 在与 `ts-loader` 一起使用时会崩溃](#getmutableclonenode-blows-up-when-used-with-ts-loader)\n\n\u003C!-- tocstop -->\n\n# 引言\n\nTypeScript 是 JavaScript 的带类型超集，最终会被编译成普通的 JavaScript。TypeScript 支持消费者将代码从一种形式“转换”为另一种形式的功能，这与 [Babel](https:\u002F\u002Fbabeljs.io\u002F) 使用“插件”来实现类似。\n\n> 关注我 [@itsmadou](https:\u002F\u002Ftwitter.com\u002Fitsmadou)，获取最新动态和讨论\n\n## 运行示例\n\n本手册中提供了多个可供使用的示例。如果你想深入学习，请确保：\n\n1. 克隆仓库\n2. 使用 `yarn` 安装依赖\n3. 构建你想要的示例：`yarn build example_name`\n\n# 基础知识\n\n简单来说，转换器本质上是一个接收并返回一段代码的函数，例如：\n\n```js\nconst Transformer = code => code;\n```\n\n但不同的是，这里的 `code` 并不是字符串类型，而是以抽象语法树（AST）的形式存在，如下文所述。借助 AST，我们可以执行强大的操作，比如更新、替换、添加和删除节点。\n\n## 什么是抽象语法树 (AST)\n\n抽象语法树，简称 AST，是一种描述已解析代码的数据结构。在使用 TypeScript 处理 AST 时，强烈建议使用 AST 查看工具，例如 [ts-ast-viewer.com](https:\u002F\u002Fts-ast-viewer.com)。\n\n通过这样的工具，我们可以看到以下代码：\n\n```js\nfunction hello() {\n  console.log('world');\n}\n```\n\n其 AST 表示如下：\n\n```\n-> SourceFile\n  -> FunctionDeclaration\n      - Identifier\n  -> Block\n    -> ExpressionStatement\n      -> CallExpression\n        -> PropertyAccessExpression\n            - Identifier\n            - Identifier\n          - StringLiteral\n  - EndOfFileToken\n```\n\n要更详细地查看 AST，可以亲自访问 [ts-ast-viewer.com](https:\u002F\u002Fts-ast-viewer.com\u002F#code=GYVwdgxgLglg9mABACwKYBt10QCgJSIDeAUImYhAgM5zqoB0WA5jgOQDucATugCat4A3MQC)! 你还可以在左下角看到用于生成相同 AST 的代码，在右侧面板中查看所选节点的元数据。非常实用！\n\n查看元数据时，你会发现它们都具有相似的结构（省略了一些属性）：\n\n```js\n{\n  kind: 307, \u002F\u002F (SyntaxKind.SourceFile)\n  pos: 0,\n  end: 47,\n  statements: [{...}],\n}\n```\n\n```js\n{\n  kind: 262, \u002F\u002F (SyntaxKind.FunctionDeclaration)\n  pos: 0,\n  end: 47,\n  name: {...},\n  body: {...},\n}\n```\n\n```js\n{\n  kind: 244, \u002F\u002F (SyntaxKind.ExpressionStatement)\n  pos: 19,\n  end: 45,\n  expression: {...}\n}\n```\n\n> `SyntaxKind` 是 TypeScript 中的一个枚举类型，用于描述节点的种类。有关更多信息，请参阅 Basarat 的 AST 提示：[AST 提示 — SyntaxKind](https:\u002F\u002Fbasarat.gitbook.io\u002Ftypescript\u002Foverview\u002Fast\u002Fast-tip-syntaxkind)。\n\n以此类推。每一个条目都描述了一个“节点”。AST 可以由一个或多个节点组成，它们共同描述了程序的语法，可用于静态分析。\n\n每个节点都有一个 `kind` 属性，用于描述节点的类型；此外还有 `pos` 和 `end` 属性，分别表示该节点在源代码中的起始和结束位置。我们将在手册的后续部分讨论如何将节点缩小到特定类型。\n\n## 阶段\n\n与 Babel 非常相似——\n不过 TypeScript 有五个阶段：\n**解析器**、\n_绑定器_、\n_检查器_、\n**转换**、\n**输出**。\n\n其中有两个步骤是 TypeScript 特有的，\n即 _绑定器_ 和 _检查器_。\n由于 _检查器_ 涉及 TypeScript 类型检查的具体实现细节，我们在此将略过不表。\n\n> 如果想更深入地了解 TypeScript 编译器的内部机制，可以阅读 [Basarat 的手册](https:\u002F\u002Fbasarat.gitbook.io\u002Ftypescript\u002F)。\n\n### TypeScript 中的“程序”\n\n在继续之前，我们需要快速明确一下，在 TypeScript 中，“程序”究竟指的是什么。\n一个“程序”是由一个或多个入口源文件组成的集合，这些文件会引入一个或多个模块。\n整个集合会在每个编译阶段中被使用。\n\n这与 Babel 处理文件的方式不同——\nBabel 是逐个文件输入、逐个文件输出，\n而 TypeScript 则是以整个项目为单位进行处理。\n这就是为什么例如用 Babel 解析 TypeScript 时，枚举无法正常工作的原因，\n因为 Babel 并没有获取到所有必要的信息。\n\n### 解析器\n\nTypeScript 的解析器实际上由两部分组成：\n`scanner`（词法分析器）和 `parser`（语法分析器）。\n这一步骤会将源代码转换为抽象语法树（AST）。\n\n```\n源代码 ~~ scanner ~~> 令牌流 ~~ parser ~~> AST\n```\n\n语法分析器接收源代码，并尝试将其转换为内存中的 AST 表示形式，以便在编译器中进一步处理。更多信息请参阅 [Parser](https:\u002F\u002Fbasarat.gitbooks.io\u002Ftypescript\u002Fcontent\u002Fdocs\u002Fcompiler\u002Fparser.html)。\n\n### 词法分析器\n\n词法分析器由语法分析器使用，以线性方式将字符串转换成一系列令牌；\n随后由语法分析器负责将这些令牌组织成一棵树状结构。更多信息请参阅 [Scanner](https:\u002F\u002Fbasarat.gitbooks.io\u002Ftypescript\u002Fdocs\u002Fcompiler\u002Fscanner.html)。\n\n### 绑定器\n\n绑定器会创建符号映射，并利用 AST 为类型系统提供支持，这对于链接引用以及识别导入和导出节点至关重要。\n更多信息请参阅 [Binder](https:\u002F\u002Fbasarat.gitbooks.io\u002Ftypescript\u002Fdocs\u002Fcompiler\u002Fbinder.html)。\n\n### 转换\n\n这正是我们关注的步骤。\n它允许开发者以任何合适的方式修改代码。\n无论是性能优化、编译时行为，还是其他任何我们可以想象的操作，都可以在这里实现。\n\n我们主要关心三个转换阶段：\n\n- `before`：在 TypeScript 自带的转换器之前运行（代码尚未被编译）。\n- `after`：在 TypeScript 自带的转换器之后运行（代码已被编译）。\n- `afterDeclarations`：在 **声明** 步骤之后运行（你可以在这里转换类型定义）。\n\n通常情况下，90% 的场景下我们都会编写 `before` 阶段的转换器，\n但如果你需要在编译后进行一些转换，或者修改类型，\n那么你可能会选择使用 `after` 和 `afterDeclarations` 阶段。\n\n> **提示**：类型检查不应该发生在转换之后。\n> 如果发生了这种情况，很可能是出现了 bug——请提交 issue！\n\n### 输出\n\n这一阶段位于最后，负责将最终的代码输出到某个位置。\n通常情况下，输出会写入文件系统，\n但也可能是在内存中。\n\n## 遍历\n\n如果想要以任何方式修改 AST，就需要对这棵树进行递归遍历。\n具体来说，就是访问每一个节点，\n然后返回原节点、更新后的节点，或者全新的节点。\n\n以下是一个 JSON 格式的 AST 示例（省略了一些值）：\n\n```js\n{\n  kind: 307, \u002F\u002F (SyntaxKind.SourceFile)\n  statements: [{\n    kind: 262, \u002F\u002F (SyntaxKind.FunctionDeclaration)\n    name: {\n      kind: 80 \u002F\u002F (SyntaxKind.Identifier)\n      escapedText: \"hello\"\n    },\n    body: {\n      kind: 241, \u002F\u002F (SyntaxKind.Block)\n      statements: [{\n        kind: 244, \u002F\u002F (SyntaxKind.ExpressionStatement)\n        expression: {\n          kind: 213, \u002F\u002F (SyntaxKind.CallExpression)\n          expression: {\n            kind: 211, \u002F\u002F (SyntaxKind.PropertyAccessExpression)\n            name: {\n              kind: 80 \u002F\u002F (SyntaxKind.Identifier)\n              escapedText: \"log\",\n            },\n            expression: {\n              kind: 80, \u002F\u002F (SyntaxKind.Identifier)\n              escapedText: \"console\",\n            }\n          }\n        },\n        arguments: [{\n          kind: 11, \u002F\u002F (SyntaxKind.StringLiteral)\n          text: \"world\",\n        }]\n      }]\n    }\n  }]\n}\n```\n\n如果我们对其进行遍历，就会从 `SourceFile` 开始，依次访问每个节点。\n你可能会认为自己可以手动逐级访问，比如 `source.statements[0].name` 等，\n但你会发现这种方式难以扩展，且极易出错——因此请谨慎使用。\n\n对于大多数情况而言，最好使用 TypeScript 内置的方法来遍历 AST。\nTypeScript 提供了两种主要的遍历方法：\n\n### `visitNode()`\n\n通常你只需要传入初始的 `SourceFile` 节点即可。\n稍后我们会详细介绍 `visitor` 函数的作用。\n\n```ts\nimport * as ts from 'typescript';\n\nts.visitNode(sourceFile, visitor, test);\n```\n\n### `visitEachChild()`\n\n这是一个特殊的函数，它内部会调用 `visitNode`。\n它可以自动向下遍历到最深层的节点，\n并且无需你额外思考如何实现。\n稍后我们会介绍 `context` 对象的作用。\n\n```ts\nimport * as ts from 'typescript';\n\nts.visitEachChild(node, visitor, context);\n```\n\n### `visitor`\n\n[访问者模式](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FVisitor_pattern) 是你在编写每个转换器时都会用到的模式，\n幸运的是，TypeScript 已经帮我们封装好了这部分逻辑，我们只需提供一个回调函数即可。\n我们可以编写一个最简单的函数如下：\n\n```ts\nimport * as ts from 'typescript';\n\nconst transformer = sourceFile => {\n  const visitor = (node: ts.Node): ts.Node => {\n    console.log(node.kind, `\\t# ts.SyntaxKind.${ts.SyntaxKind[node.kind]}`);\n    return ts.visitEachChild(node, visitor, context);\n  };\n\n  return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n};\n```\n\n> **注意**：你会看到我们对每个节点都进行了返回。\n> 这是必须的！\n> 如果没有返回，就会出现奇怪的错误。\n\n如果我们把这个转换应用到前面的例子中，控制台将会输出以下内容（注释为事后添加）：\n\n```sh\n307 \t# ts.SyntaxKind.SourceFile\n262 \t# ts.SyntaxKind.FunctionDeclaration\n80  \t# ts.SyntaxKind.Identifier\n241 \t# ts.SyntaxKind.Block\n244 \t# ts.SyntaxKind.ExpressionStatement\n213 \t# ts.SyntaxKind.CallExpression\n211 \t# ts.SyntaxKind.PropertyAccessExpression\n80  \t# ts.SyntaxKind.Identifier\n80  \t# ts.SyntaxKind.Identifier\n11  \t# ts.SyntaxKind.StringLiteral\n```\n\n> **提示**：你可以在 [\u002Fexample-transformers\u002Flog-every-node](\u002Fexample-transformers\u002Flog-every-node) 查看该示例的源码——如果想在本地运行，可以使用 `yarn build log-every-node` 命令。\n\n它会尽可能深入地进入每一个节点，\n直到到达最底层，\n然后再进入下一个子节点。\n\n### `context`\n\n每个转换器都会接收到一个转换上下文 `context`。\n这个上下文不仅用于 `visitEachChild`，还能帮助我们获取当前的 TypeScript 配置等有用信息。\n我们很快就会看到第一个简单的 TypeScript 转换器示例。\n\n## 作用域\n\n> 本部分内容大多直接摘自 [Babel 手册](https:\u002F\u002Fgithub.com\u002Fjamiebuilds\u002Fbabel-handbook\u002Fblob\u002Fmaster\u002Ftranslations\u002Fen\u002Fplugin-handbook.md#scopes)，因为其中的原则同样适用。\n\n接下来我们介绍作用域的概念（[作用域](\u003Chttps:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FScope_(computer_science)>)）。\nJavaScript 使用词法作用域（[闭包](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FJavaScript\u002FClosures)），它是一种树状结构，块级作用域会创建新的作用域。\n\n```js\n\u002F\u002F 全局作用域\n\nfunction scopeOne() {\n  \u002F\u002F 作用域 1\n\n  function scopeTwo() {\n    \u002F\u002F 作用域 2\n  }\n}\n```\n\n在 JavaScript 中，无论你是通过变量、函数、类、参数、导入、标签等方式创建引用，它们都属于当前的作用域。\n\n```js\nvar global = '我在全局作用域中';\n\nfunction scopeOne() {\n  var one = '我在 `scopeOne()` 创建的作用域中';\n\n  function scopeTwo() {\n    var two = '我在 `scopeTwo()` 创建的作用域中';\n  }\n}\n```\n\n嵌套更深的作用域中的代码可以使用外层作用域中的引用。\n\n```js\nfunction scopeOne() {\n  var one = '我在 `scopeOne()` 创建的作用域中';\n\n  function scopeTwo() {\n    one = '我在 `scopeTwo()` 中更新了 `scopeOne` 的引用';\n  }\n}\n```\n\n较低的作用域也可以创建同名的引用，而不会影响外层作用域的引用。\n\n```js\nfunction scopeOne() {\n  var one = '我在 `scopeOne()` 创建的作用域中';\n\n  function scopeTwo() {\n    var one = '我正在创建一个新的 `one`，但不改变 `scopeOne()` 中的引用。';\n  }\n}\n```\n\n编写转换器时，我们需要特别注意作用域问题。在修改代码的不同部分时，必须确保不会破坏现有代码的逻辑。\n\n有时我们可能需要添加新的引用，并确保它们不会与现有的引用发生冲突。或者只是想找到某个变量被引用的位置。因此，我们需要能够在特定的作用域内跟踪这些引用。\n\n### 绑定\n\n引用都属于特定的作用域，这种关系被称为绑定。\n\n```js\nfunction scopeOnce() {\n  var ref = '这是一个绑定';\n\n  ref; \u002F\u002F 这是对绑定的引用\n\n  function scopeTwo() {\n    ref; \u002F\u002F 这是来自较低作用域的对绑定的引用\n  }\n}\n```\n\n# 转换器 API\n\n编写转换器时，建议使用 TypeScript 来实现。我们将主要借助 `typescript` 包来完成大部分工作，因为它几乎涵盖了所有功能，而 Babel 则依赖于多个独立的小型包。\n\n首先，安装 `typescript`：\n\n```sh\nnpm i typescript --save\n```\n\n然后导入它：\n\n```ts\nimport * as ts from 'typescript';\n```\n\n> **提示**：强烈推荐使用 VSCode 的智能感知功能来探索 API，这非常有帮助！\n\n## 访问节点\n\n以下方法用于访问 AST 节点——我们已经在前面简要介绍过其中一些。\n\n- `ts.visitNode(node, visitor, test)`：用于访问根节点，通常是 `SourceFile`。\n- `ts.visitEachChild(node, visitor, context)`：用于访问节点的每个子节点。\n- `ts.isXyz(node)`：用于缩小节点的类型范围，例如 `ts.isVariableDeclaration(node)`。\n\n## 操作节点\n\n这些方法用于以某种方式修改节点。\n\n- `ts.factory.createXyz(...)`：用于创建新节点并返回，例如 `ts.factory.createIdentifier('world')`。\n- `ts.factory.updateXyz(node, ...)`：用于更新节点并返回，例如 `ts.factory.updateVariableDeclaration()`。\n- `ts.factory.updateSourceFile(sourceFile, ...)`：用于更新源文件并返回。\n- `ts.setOriginalNode(newNode, originalNode)`：用于设置节点的原始节点。\n- `ts.setXyz(...)`：用于设置某些属性。\n- `ts.addXyz(...)`：用于添加某些内容。\n\n## `context`\n\n如上所述，`context` 会传递给每个转换器，并提供一些实用的方法（以下并非完整列表，仅列出我们关注的部分）：\n\n- `getCompilerOptions()`：获取传递给转换器的编译器选项。\n- `hoistFunctionDeclaration(node)`：将函数声明提升到包含它的作用域顶部。\n- `hoistVariableDeclaration(node)`：将变量声明提升到包含它的作用域顶部。\n\n## `program`\n\n这是编写 Program 类型转换器时可用的一个特殊属性。我们将在“转换器类型”一节中详细介绍这种类型的转换器。它包含了关于整个程序的元数据，例如（以下并非完整列表，仅列出我们关注的部分）：\n\n- `getRootFileNames()`：获取项目中所有文件的名称数组。\n- `getSourceFiles()`：获取项目中的所有 `SourceFile`。\n- `getCompilerOptions()`：从 `tsconfig.json`、命令行或其他来源获取编译器选项（也可以通过 `context` 获取）。\n- `getSourceFile(fileName: string)`：根据文件名获取对应的 `SourceFile`。\n- `getSourceFileByPath(path: Path)`：根据路径获取对应的 `SourceFile`。\n- `getCurrentDirectory()`：获取当前目录的字符串。\n- `getTypeChecker()`：获取类型检查器，这在处理 [符号](https:\u002F\u002Fbasarat.gitbooks.io\u002Ftypescript\u002Fcontent\u002Fdocs\u002Fcompiler\u002Fbinder.html) 时非常有用。\n\n## `typeChecker`\n\n它是调用 `program.getTypeChecker()` 后得到的对象。它包含许多有趣且在编写转换器时会用到的信息。\n\n- `getSymbolAtLocation(node)`：用于获取节点的符号。\n- `getExportsOfModule(symbol)`：返回模块符号的导出内容。\n\n# 编写你的第一个转换器\n\n这正是我们一直期待的部分！\n让我们来编写我们的第一个转换器。\n\n首先，我们导入 `typescript`。\n\n```ts\nimport * as ts from 'typescript';\n```\n\n它将包含我们在编写转换器时可能用到的所有内容。\n\n接下来，我们创建一个默认导出，它将成为我们的转换器。\n我们的初始转换器将是一个转换器工厂（因为这样我们可以访问 `context`）——其他类型的转换器我们稍后再讨论。\n\n```ts\nimport * as ts from 'typescript';\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n    \u002F\u002F 转换代码在这里\n  };\n};\n\nexport default transformer;\n```\n\n由于我们使用 TypeScript 来编写转换器，\n我们获得了类型安全，更重要的是智能感知！\n如果你已经做到这里，你会发现 TypeScript 抱怨我们没有返回一个 `SourceFile`——让我们修复这个问题。\n\n```diff\nimport * as ts from \"typescript\";\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n+    return sourceFile;\n  };\n};\n\nexport default transformer;\n```\n\n太好了，我们修复了类型错误！\n\n对于我们的第一个转换器，我们将借鉴 [Babel 手册](https:\u002F\u002Fgithub.com\u002Fjamiebuilds\u002Fbabel-handbook\u002Fblob\u002Fmaster\u002Ftranslations\u002Fen\u002Fplugin-handbook.md#writing-your-first-babel-plugin) 的思路，重命名一些标识符。\n\n这是我们的源代码：\n\n```ts\nbabel === plugins;\n```\n\n让我们编写一个访问者函数，\n记住，访问者函数应该接受特定类型的 `node`（这里是 `SourceFile`），\n然后返回相同类型的 `node`。注意，`visitNode` 的 `test` 参数可以用来确保返回特定类型的节点。\n\n```diff\nimport * as ts from 'typescript';\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n+    const visitor = (node: ts.Node): ts.Node => {\n+      return node;\n+    };\n+\n+    return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n-\n-    return sourceFile;\n  };\n};\n\nexport default transformer;\n```\n\n好的，这会访问 `SourceFile`……\n然后立即返回它。\n这有点没用——让我们确保访问每个节点吧！\n\n```diff\nimport * as ts from 'typescript';\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n    const visitor = (node: ts.Node): ts.Node => {\n-      return node;\n+      return ts.visitEachChild(node, visitor, context);\n    };\n\n    return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n  };\n};\n\nexport default transformer;\n```\n\n现在让我们找到标识符以便重命名它们：\n\n```diff\nimport * as ts from 'typescript';\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n    const visitor = (node: ts.Node): ts.Node => {\n+      if (ts.isIdentifier(node)) {\n+        \u002F\u002F 在这里进行转换\n+      }\n\n      return ts.visitEachChild(node, visitor, context);\n    };\n\n    return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n  };\n};\n\nexport default transformer;\n```\n\n然后，让我们针对我们感兴趣的特定标识符：\n\n```diff\nimport * as ts from 'typescript';\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n    const visitor = (node: ts.Node): ts.Node => {\n      if (ts.isIdentifier(node)) {\n+        switch (node.escapedText) {\n+          case 'babel':\n+            \u002F\u002F 重命名 babel\n+\n+          case 'plugins':\n+            \u002F\u002F 重命名 plugins\n+        }\n      }\n\n      return ts.visitEachChild(node, visitor, context);\n    };\n\n    return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n  };\n};\n\nexport default transformer;\n```\n\n最后，让我们返回已重命名的新节点！\n\n```diff\nimport * as ts from 'typescript';\n\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n    const visitor = (node: ts.Node): ts.Node => {\n      if (ts.isIdentifier(node)) {\n        switch (node.escapedText) {\n          case 'babel':\n+            return ts.factory.createIdentifier('typescript');\n\n          case 'plugins':\n+            return ts.factory.createIdentifier('transformers');\n        }\n      }\n\n      return ts.visitEachChild(node, visitor, context);\n    };\n\n    return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n  };\n};\n\nexport default transformer;\n```\n\n太棒了！\n当我们将其应用于我们的源代码时，会得到以下输出：\n\n```ts\ntypescript === transformers;\n```\n\n> **提示** - 你可以查看此示例的源代码：[\u002Fexample-transformers\u002Fmy-first-transformer](\u002Fexample-transformers\u002Fmy-first-transformer)。如果想在本地运行，可以通过 `yarn build my-first-transformer` 来执行。\n\n# 转换器的类型\n\n所有转换器最终都会返回 `TransformerFactory` 类型签名。\n这些转换器类型取自 [`ttypescript`](https:\u002F\u002Fgithub.com\u002Fcevek\u002Fttypescript)。\n\n## 工厂\n\n也称为 `raw`，\n这与我们在编写第一个转换器时使用的类型相同。\n\n```ts\n\u002F\u002F ts.TransformerFactory\n(context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile;\n```\n\n## 配置\n\n当你的转换器需要由使用者控制的配置时。\n\n```ts\n(config?: YourPluginConfigInterface) => ts.TransformerFactory;\n```\n\n## 程序\n\n当你需要访问 `program` 对象时，你应该使用这种签名，\n它应该返回一个 `TransformerFactory`。\n它还具有作为第二个参数提供的、由使用者提供的配置。\n\n```ts\n(program: ts.Program, config?: YourPluginConfigInterface) => ts.TransformerFactory;\n```\n\n# 使用转换器\n\n有趣的是，TypeScript 并没有官方支持通过 `tsconfig.json` 使用转换器。\n有一个 [GitHub 问题](https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FTypeScript\u002Fissues\u002F14419) 专门讨论引入相关功能。\n尽管如此，你仍然可以使用转换器，只是方式稍微绕了一点。\n\n## [`ts-patch`](https:\u002F\u002Fgithub.com\u002Fnonara\u002Fts-patch)\n\n> **这是推荐的方法**！\n> 希望未来能正式支持在 `typescript` 中使用。\n\n本质上是 `tsc` CLI 的封装——它通过 `tsconfig.json` 提供对转换器的一流支持。它将 `typescript` 列为 peer dependency，因此理论上不会过于脆弱。\n\n安装：\n\n```sh\nnpm i ts-patch -D\n```\n\n将你的转换器添加到编译器选项中：\n\n```json\n{\n  \"compilerOptions\": {\n    \"plugins\": [{ \"transform\": \"my-first-transformer\" }]\n  }\n}\n```\n\n运行 `tspc`：\n\n```sh\ntspc\n```\n\n`ts-patch` 支持 `tsc` CLI、Webpack、Rollup、Jest 和 VSCode。说实话，几乎涵盖了我们所有可能的使用场景。\n\n# 转换操作\n\n## 访问\n\n### 检查节点是否为特定类型\n\n有许多辅助方法可以断言节点的类型。当这些方法返回 `true` 时，它们会“收窄” `node` 的类型，从而可能为你提供基于该类型的额外属性和方法。\n\n> **提示**：充分利用智能感知来查看 `ts` 导入中可用的方法，同时使用 [TypeScript AST 查看器](https:\u002F\u002Fts-ast-viewer.com\u002F) 来了解节点的具体类型。\n\n```ts\nimport * as ts from 'typescript';\n\nconst visitor = (node: ts.Node): ts.Node => {\n  if (ts.isJsxAttribute(node.parent)) {\n    \u002F\u002F node.parent 是一个 JSX 属性\n    \u002F\u002F ...\n  }\n};\n```\n\n### 检查两个标识符是否引用同一个符号\n\n标识符由解析器创建，并且总是唯一的。例如，如果你创建了一个变量 `foo`，并在另一行再次使用它，那么将会生成两个具有相同文本内容 `foo` 的独立标识符。\n\n随后，链接器会遍历这些标识符，并将引用同一变量的标识符通过一个共同的符号连接起来（同时考虑作用域和遮蔽）。可以将符号理解为我们直观上所认为的变量。\n\n因此，要检查两个标识符是否引用同一个符号，只需获取与标识符相关的符号，并通过引用比较它们是否相同。\n\n**简短示例**：\n\n```ts\nconst symbol1 = typeChecker.getSymbolAtLocation(node1);\nconst symbol2 = typeChecker.getSymbolAtLocation(node2);\n\nsymbol1 === symbol2; \u002F\u002F 通过引用比较\n```\n\n**完整示例**：\n\n以下代码会记录所有重复的符号。\n\n```ts\nimport * as ts from 'typescript';\n\nconst transformerProgram = (program: ts.Program) => {\n  const typeChecker = program.getTypeChecker();\n\n  \u002F\u002F 创建已找到符号的数组\n  const foundSymbols = new Array\u003Cts.Symbol>();\n\n  const transformerFactory: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n    return sourceFile => {\n      const visitor = (node: ts.Node): ts.Node => {\n        if (ts.isIdentifier(node)) {\n          const relatedSymbol = typeChecker.getSymbolAtLocation(node);\n\n          \u002F\u002F 检查数组中是否已包含相同的符号——通过引用比较\n          if (foundSymbols.includes(relatedSymbol)) {\n            const foundIndex = foundSymbols.indexOf(relatedSymbol);\n            console.log(\n              `在位置 ${foundIndex} 找到了现有符号，名称为 \"${relatedSymbol.name}\"`\n            );\n          } else {\n            \u002F\u002F 如果未找到，则将其添加到数组中\n            foundSymbols.push(relatedSymbol);\n\n            console.log(\n              `找到了新符号，名称为 \"${relatedSymbol.name}\"。已添加到位置 ${foundSymbols.length - 1}`\n            );\n          }\n\n          return node;\n        }\n\n        return ts.visitEachChild(node, visitor, context);\n      };\n\n      return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n    };\n  };\n\n  return transformerFactory;\n};\n\nexport default transformerProgram;\n```\n\n> **提示**：你可以在 [\u002Fexample-transformers\u002Fmatch-identifier-by-symbol](\u002Fexample-transformers\u002Fmatch-identifier-by-symbol) 中查看此示例的源代码；如果想在本地运行，可以使用 `yarn build match-identifier-by-symbol` 命令。\n\n### 查找特定父节点\n\n虽然没有现成的方法可以直接实现这一功能，但你可以自己编写一个。给定一个节点：\n\n```ts\nconst findParent = (node: ts.Node, predicate: (node: ts.Node) => boolean) => {\n  if (!node.parent) {\n    return undefined;\n  }\n\n  if (predicate(node.parent)) {\n    return node.parent;\n  }\n\n  return findParent(node.parent, predicate);\n};\n\nconst visitor = (node: ts.Node): ts.Node => {\n  if (ts.isStringLiteral(node)) {\n    const parent = findParent(node, ts.isFunctionDeclaration);\n    if (parent) {\n      console.log('字符串字面量有一个函数声明作为父节点');\n    }\n    return node;\n  }\n};\n```\n\n对于以下源代码：\n\n```ts\nfunction hello() {\n  if (true) {\n    'world';\n  }\n}\n```\n\n上述代码会在控制台输出 `字符串字面量有一个函数声明作为父节点`。\n\n- 替换节点后进行遍历时需小心——`parent` 可能未被正确设置。如果需要在转换后继续遍历，请确保手动为节点设置 `parent` 属性。\n\n> **提示**：你可以在 [\u002Fexample-transformers\u002Ffind-parent](\u002Fexample-transformers\u002Ffind-parent) 中查看此示例的源代码；如果想在本地运行，可以使用 `yarn build find-parent` 命令。 \n\n### 停止遍历\n\n在访问者函数中，你可以提前返回而不继续遍历子节点。例如，当我们遇到某个节点并且知道无需再深入时：\n\n```ts\nconst visitor = (node: ts.Node): ts.Node => {\n  if (ts.isArrowFunction(node)) {\n    \u002F\u002F 提前返回\n    return node;\n  }\n};\n```\n\n## 操作\n\n### 更新节点\n\n```ts\nif (ts.isVariableDeclaration(node)) {\n  return ts.updateVariableDeclaration(\n    node, \n    node.name, \n    undefined, \n    node.type, \n    ts.createStringLiteral('world')\n  );\n}\n```\n\n```diff\n-const hello = true;\n+const hello = \"updated-world\";\n```\n\n> **提示**：你可以在 [\u002Fexample-transformers\u002Fupdate-node](\u002Fexample-transformers\u002Fupdate-node) 中查看此示例的源代码；如果想在本地运行，可以使用 `yarn build update-node` 命令。\n\n### 替换节点\n\n有时我们可能希望完全替换一个节点，而不是仅仅更新它。这时只需返回一个新的节点即可！\n\n```ts\nif (ts.isFunctionDeclaration(node)) {\n  \u002F\u002F 将找到的任何函数替换为箭头函数。\n  return ts.factory.createVariableDeclarationList(\n    [\n      ts.factory.createVariableDeclaration(\n        ts.factory.createIdentifier(node.name.escapedText),\n        undefined,\n        ts.factory.createArrowFunction(\n          undefined,\n          undefined,\n          [],\n          undefined,\n          ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),\n          ts.factory.createBlock([], false)\n        )\n      ),\n    ],\n    ts.NodeFlags.Const\n  );\n}\n```\n\n```diff\n-function helloWorld() {}\n+const helloWorld = () => {};\n```\n\n> **提示**：你可以在 [\u002Fexample-transformers\u002Freplace-node](\u002Fexample-transformers\u002Freplace-node) 中查看此示例的源代码；如果想在本地运行，可以使用 `yarn build replace-node` 命令。\n\n### 用多个节点替换一个节点\n\n有趣的是，访问者函数不仅可以返回一个节点，还可以返回一个节点数组。\n这意味着，即使它接收一个节点作为输入，也可以返回多个节点来替换该输入节点。\n\n```ts\ntype Visitor\u003CTIn extends Node = Node, TOut extends Node | undefined = TIn | undefined> = \n  (node: TIn) => VisitResult\u003CTOut>;\ntype VisitResult\u003CT extends Node | undefined> = T | readonly Node[];\n```\n\n让我们把每个表达式语句都替换成两个相同的语句（即复制该语句）：\n\n```ts\nconst transformer: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n  return sourceFile => {\n    const visitor = (node: ts.Node): ts.VisitResult\u003Cts.Node> => {\n      \u002F\u002F 如果是表达式语句，\n      if (ts.isExpressionStatement(node)) {\n        \u002F\u002F 返回两次该语句。\n        \u002F\u002F 实际上就是复制了该语句\n        return [node, node];\n      }\n\n      return ts.visitEachChild(node, visitor, context);\n    };\n\n    return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n  };\n};\n```\n\n因此，\n\n```ts\nlet a = 1;\na = 2;\n```\n\n会变成\n\n```js\nlet a = 1;\na = 2;\na = 2;\n```\n\n> **提示** - 您可以在 [\u002Fexample-transformers\u002Freturn-multiple-node](\u002Fexample-transformers\u002Freturn-multiple-node) 查看此示例的源代码。如果想在本地运行，可以使用 `yarn build return-multiple-node` 命令。\n\n声明语句（第一行）会被忽略，因为它不是 `ExpressionStatement`。\n\n_注意_ - 请确保您在 AST 中的操作是有意义的。例如，用两个表达式代替一个通常是无效的。\n\n假设有一个赋值表达式（带有 EqualToken 操作符的 BinaryExpression），如 `a = b = 2`。现在如果用两个节点代替 `b = 2` 表达式，就会导致无效（因为右侧不能是多个节点）。因此，TS 会抛出错误：`Debug Failure. False expression: Too many nodes written to output.`\n\n### 插入兄弟节点\n\n这实际上与[上一节](#replacing-a-node-with-multiple-nodes)相同。只需返回包含自身和其他兄弟节点的节点数组即可。\n\n### 删除节点\n\n如果您不再需要某个特定节点，该怎么办呢？\n只需返回 `undefined`！\n\n```ts\nif (ts.isImportDeclaration(node)) {\n  \u002F\u002F 将移除所有 import 声明\n  return undefined;\n}\n```\n\n```diff\nimport lodash from 'lodash';\n-import lodash from 'lodash';\n```\n\n> **提示** - 您可以在 [\u002Fexample-transformers\u002Fremove-node](\u002Fexample-transformers\u002Fremove-node) 查看此示例的源代码。如果想在本地运行，可以使用 `yarn build remove-node` 命令。\n\n### 添加新的 import 声明\n\n有时您的转换可能需要一些运行时依赖，为此您可以添加自己的 import 声明。\n\n```ts\nts.factory.updateSourceFile(sourceFile, [\n  ts.factory.createImportDeclaration(\n    \u002F* 修饰符 *\u002F undefined,\n    ts.factory.createImportClause(\n      false,\n      ts.factory.createIdentifier('DefaultImport'),\n      ts.factory.createNamedImports([\n        ts.factory.createImportSpecifier(\n          false, \n          undefined, \n          ts.factory.createIdentifier('namedImport')\n        ),\n      ])\n    ),\n    ts.factory.createStringLiteral('package')\n  ),\n  \u002F\u002F 确保源文件中的其余语句仍然存在。\n  ...sourceFile.statements,\n]);\n```\n\n```diff\n+import DefaultImport, { namedImport } from \"package\";\n```\n\n> **提示** - 您可以在 [\u002Fexample-transformers\u002Fadd-import-declaration](\u002Fexample-transformers\u002Fadd-import-declaration) 查看此示例的源代码。如果想在本地运行，可以使用 `yarn build add-import-declaration` 命令。\n\n## 作用域\n\n### 将变量声明提升到其作用域顶部\n\n有时您可能希望将 `VariableDeclaration` 提升上去以便对其进行赋值。请记住，这只会提升变量声明本身——赋值操作仍然保留在源代码中的原位置。\n\n```ts\nif (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {\n  context.hoistVariableDeclaration(node.name);\n  return node;\n}\n```\n\n```diff\nfunction functionOne() {\n+  var innerOne;\n+  var innerTwo;\n  const innerOne = true;\n  const innerTwo = true;\n}\n```\n\n> **提示** - 您可以在 [\u002Fexample-transformers\u002Fhoist-variable-declaration](\u002Fexample-transformers\u002Fhoist-variable-declaration) 查看此示例的源代码。如果想在本地运行，可以使用 `yarn build hoist-variable-declaration` 命令。\n\n您也可以对函数声明执行类似的操作：\n\n```ts\nif (ts.isFunctionDeclaration(node)) {\n  context.hoistFunctionDeclaration(node);\n  return node;\n}\n```\n\n```diff\n+function functionOne() {\n+    console.log('hello, world!');\n+}\nif (true) {\n  function functionOne() {\n    console.log('hello, world!');\n  }\n}\n```\n\n> **提示** - 您可以在 [\u002Fexample-transformers\u002Fhoist-function-declaration](\u002Fexample-transformers\u002Fhoist-function-declaration) 查看此示例的源代码。如果想在本地运行，可以使用 `yarn build hoist-function-declaration` 命令。\n\n### 将变量声明提升到父作用域\n\n> **TODO** - 这是否可行？\n\n### 检查局部变量是否被引用\n\n> **TODO** - 这是否可行？\n\n### 定义唯一变量\n\n有时您希望添加一个在其作用域内具有唯一名称的新变量，幸运的是，无需任何复杂操作即可实现。\n\n```ts\nif (ts.isVariableDeclarationList(node)) {\n  return ts.factory.updateVariableDeclarationList(node, [\n    ...node.declarations,\n    ts.factory.createVariableDeclaration(\n      ts.factory.createUniqueName('hello'),\n      undefined \u002F* 感叹号 *\u002F,\n      undefined \u002F* 类型 *\u002F,\n      ts.factory.createStringLiteral('world')\n    ),\n  ]);\n}\n\nreturn ts.visitEachChild(node, visitor, context);\n```\n\n```diff\n-const hello = 'world';\n+const hello = 'world', hello_1 = \"world\";\n```\n\n> **提示** - 您可以在 [\u002Fexample-transformers\u002Fcreate-unique-name](\u002Fexample-transformers\u002Fcreate-unique-name) 查看此示例的源代码。如果想在本地运行，可以使用 `yarn build create-unique-name` 命令。\n\n### 重命名绑定及其引用\n\n> **TODO** - 是否有简洁的方法可以实现？\n\n## 查找\n\n### 获取行号和列号\n\n```\nsourceFile.getLineAndCharacterOfPosition(node.getStart());\n```\n\n## 高级用法\n\n### 求值表达式\n\n> **TODO** - 这是否可行？\n\n### 跟踪模块导入\n\n这是可行的！\n\n```ts\n\u002F\u002F 我们需要使用 Program 转换器来获取程序对象。\nconst transformerProgram = (program: ts.Program) => {\n  const transformerFactory: ts.TransformerFactory\u003Cts.SourceFile> = context => {\n    return sourceFile => {\n      const visitor = (node: ts.Node): ts.Node => {\n        if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {\n          const typeChecker = program.getTypeChecker();\n          const importSymbol = typeChecker.getSymbolAtLocation(node.moduleSpecifier)!;\n          const exportSymbols = typeChecker.getExportsOfModule(importSymbol);\n\n          exportSymbols.forEach(symbol =>\n            console.log(\n              `找到 \"${\n                symbol.escapedName\n              }\" 导出，其值为 \"${symbol.valueDeclaration!.getText()}\"`\n            )\n          );\n\n          return node;\n        }\n\n        return ts.visitEachChild(node, visitor, context);\n      };\n\n      return ts.visitNode(sourceFile, visitor, ts.isSourceFile);\n    };\n  };\n\n  return transformerFactory;\n};\n```\n\n这将在控制台输出以下内容：\n\n```\n找到 \"hello\" 导出，其值为 \"hello = 'world'\"\n找到 \"default\" 导出，其值为 \"export default 'hello';\"\n```\n\n你还可以使用 `ts.visitChild` 等方法遍历被导入的节点。\n\n> **提示** - 你可以在 [\u002Fexample-transformers\u002Ffollow-imports](\u002Fexample-transformers\u002Ffollow-imports) 查看此示例的源代码。如果想在本地运行，可以使用 `yarn build follow-imports` 命令。\n\n### 跟踪 Node.js 模块导入\n\n就像跟踪你自己代码中的 TypeScript 导入一样，有时我们也可能希望检查我们正在导入的模块内部的代码。\n\n使用与上述相同的代码，但在处理 `node_modules` 中的导入时，控制台会输出以下内容：\n\n```\n找到 \"mixin\" 导出，其值为：\nexport declare function mixin(): {\n  color: string;\n};\"\n找到 \"constMixin\" 导出，其值为：\nexport declare function constMixin(): {\n  color: 'blue';\n};\"\n```\n\n咦？为什么我们得到的是类型定义的 AST 而不是源代码呢……真扫兴！\n\n事实证明，要实现这一点稍微困难一些（至少不能直接开箱即用）。我们有两种选择：\n\n1. 在 `tsconfig` 中启用 `allowJs`，并**删除类型定义**……这样我们就能获得源代码的 AST……但同时也就失去了类型信息。因此这种方法并不理想。\n2. 创建另一个 TypeScript 程序，自己完成这项工作。\n\n**剧透：** 我们将选择方案 2。它更加稳健，并且在关闭类型检查时也能正常工作——这也是我们在那种情况下跟踪 TypeScript 导入的方式！\n\n```ts\nconst visitor = (node: ts.Node): ts.Node => {\n  if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {\n    \u002F\u002F 使用 require.resolve 找到文件系统中的导入位置\n    const pkgEntry = require.resolve(`${node.moduleSpecifier.text}`);\n\n    \u002F\u002F 创建另一个程序\n    const innerProgram = ts.createProgram([pkgEntry], {\n      \u002F\u002F 重要的是要将其设置为 true！\n      allowJs: true,\n    });\n\n    console.log(innerProgram.getSourceFile(pkgEntry)?.getText());\n\n    return node;\n  }\n\n  return ts.visitEachChild(node, visitor, context);\n};\n```\n\n这将在控制台输出以下内容：\n\n```\nexport function mixin() {\n  return { color: 'red' };\n}\n\nexport function constMixin() {\n  return { color: 'blue' }\n}\n```\n\n太棒了！顺便说一句，由于我们创建了一个“程序”，它所有的导入都会被自动跟踪！不过，如果这些模块也有类型定义，就会出现和之前一样的问题——所以如果你需要跨越多个导入，可能就需要采取更巧妙的方法了。\n\n> **提示** - 你可以在 [\u002Fexample-transformers\u002Ffollow-node-modules-imports](\u002Fexample-transformers\u002Ffollow-node-modules-imports) 查看此示例的源代码。如果想在本地运行，可以使用 `yarn build follow-node-modules-imports` 命令。\n\n### 转换 JSX\n\nTypeScript 也可以转换 [JSX](https:\u002F\u002Freactjs.org\u002Fdocs\u002Fintroducing-jsx.html)——有一些辅助方法可以帮助你入门。所有之前的遍历和操作方法都适用。\n\n- `ts.isJsxXyz(node)`\n- `ts.factory.updateJsxXyz(node, ...)`\n- `ts.factory.createJsxXyz(...)`\n\n查阅 TypeScript 文档以获取更多详细信息。关键在于你需要创建有效的 JSX——不过，只要确保你的转换器中类型是正确的，就很难出错。\n\n### 确定文件中的 pragma\n\n当你想知道文件中的 pragma 是什么以便在转换中执行某些操作时，这非常有用。例如，假设我们想知道是否使用了自定义的 `jsx` pragma：\n\n```ts\nconst transformer = sourceFile => {\n  const jsxPragma = (sourceFile as any).pragmas.get('jsx'); \u002F\u002F 关于强制转换为 `any` 的说明见下文\n  if (jsxPragma) {\n    console.log(`找到了一个使用工厂 \"${jsxPragma.arguments.factory}\" 的 jsx pragma`);\n  }\n\n  return sourceFile;\n};\n```\n\n下面的源文件会导致控制台输出 `'a jsx pragma was found using the factory \"jsx\"'`：\n\n```ts\n\u002F** @jsx jsx *\u002F\n```\n\n> **提示** - 你可以在 [\u002Fexample-transformers\u002Fpragma-check](\u002Fexample-transformers\u002Fpragma-check) 查看此示例的源代码。如果想在本地运行，可以使用 `yarn build pragma-check` 命令。\n\n截至 2019 年 12 月 29 日，`pragmas` 尚未包含在 `sourceFile` 的类型定义中，因此你需要将其强制转换为 `any` 才能访问它。\n\n### 重置文件中的 pragma\n\n有时在转换过程中，你可能希望将 pragma 恢复为默认值（在我们的例子中是 React）。我发现以下代码可以成功实现这一点：\n\n```ts\nconst transformer = sourceFile => {\n  sourceFile.pragmas.clear();\n  delete sourceFile.localJsxFactory;\n};\n```\n\n# 技巧与窍门\n\n## 组合转换器\n\n如果你像我一样，有时会希望把大型转换器拆分成更小、更易于维护的部分。幸运的是，通过一点编码技巧，我们可以做到这一点：\n\n```ts\nconst transformers = [...];\n\nfunction transformer(\n  program: ts.Program,\n): ts.TransformerFactory\u003Cts.SourceFile> {\n  return context => {\n    const initializedTransformers = transformers.map(transformer => transformer(program)(context));\n\n    return sourceFile => {\n      return initializedTransformers.reduce((source, transformer) => {\n        return transformer(source);\n      }, sourceFile);\n    };\n  };\n}\n```\n\n## 抛出语法错误以提升开发者体验\n\n> **待办事项** - 这种方式是否像 Babel 那样可行？\n> 或者我们是否可以使用 [语言服务插件](https:\u002F\u002Fgithub.com\u002FMicrosoft\u002FTypeScript\u002Fwiki\u002FWriting-a-Language-Service-Plugin)？\n\n# 测试\n\n通常情况下，对于转换器而言，单元测试的实用性相当有限。我建议编写集成测试，这样你的测试才会更加实用且具有韧性。归结起来就是：\n\n- **优先编写集成测试**，而非单元测试\n- 避免使用快照测试——只有在有意义的情况下才使用；**快照越大，其价值越低**\n- 尽量为每个测试拆解出特定的行为，并且**每个测试只断言一件事**\n\n如果你愿意，可以使用 [TypeScript 编译器 API](https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FTypeScript\u002Fwiki\u002FUsing-the-Compiler-API#a-simple-transform-function) 来设置你的转换器进行测试，不过我更推荐直接使用现成的库。\n\n## [`ts-transformer-testing-library`](https:\u002F\u002Fgithub.com\u002Fmarionebl\u002Fts-transformer-testing-library)\n\n这个库让测试转换器变得非常简单。它专为与诸如 [`jest`](https:\u002F\u002Fgithub.com\u002Ffacebook\u002Fjest) 之类的测试运行器配合使用而设计。它简化了转换器的配置流程，同时仍然允许你像编写其他软件一样编写测试。\n\n以下是一个使用该库的示例测试：\n\n```ts\nimport { Transformer } from 'ts-transformer-testing-library';\nimport transformerFactory from '..\u002Findex';\nimport pkg from '..\u002F..\u002F..\u002F..\u002Fpackage.json';\n\nconst transformer = new Transformer()\n  .addTransformer(transformerFactory)\n  .addMock({ name: pkg.name, content: `export const jsx: any = () => null` })\n  .addMock({\n    name: 'react',\n    content: `export default {} as any; export const useState = {} as any;`,\n  })\n  .setFilePath('\u002Findex.tsx');\n\nit('should add react default import if it only has named imports', () => {\n  const actual = transformer.transform(`\n    \u002F** @jsx jsx *\u002F\n    import { useState } from 'react';\n    import { jsx } from '${pkg.name}';\n\n    \u003Cdiv css={{}}>hello world\u003C\u002Fdiv>\n  `);\n\n  \u002F\u002F 我们还在这里使用 `jest-extended` 为 jest 对象添加额外的匹配器。\n  expect(actual).toIncludeRepeated('import React, { useState } from \"react\"', 1);\n});\n```\n\n# 已知问题\n\n## EmitResolver 无法处理并非源自解析树的 `JsxOpeningLikeElement` 和 `JsxOpeningFragment`\n\n如果你用一个新的 JSX 元素替换某个节点，例如：\n\n```tsx\nconst visitor = node => {\n  return ts.factory.createJsxFragment(\n    ts.factory.createJsxOpeningFragment(), \n    [], \n    ts.factory.createJsxJsxClosingFragment()\n  );\n};\n```\n\n那么，如果周围存在 `const` 或 `let` 变量，就会导致程序崩溃。一个 workaround 是确保将开始和结束标签传递给 `ts.setOriginalNode`：\n\n```diff\nts.createJsxFragment(\n-  ts.createJsxOpeningFragment(),\n+  ts.setOriginalNode(ts.factory.createJsxOpeningFragment(), node),\n  [],\n-  ts.createJsxJsxClosingFragment()\n+  ts.setOriginalNode(ts.factory.createJsxJsxClosingFragment(), node)\n);\n```\n\n更多信息请参阅：[https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FTypeScript\u002Fissues\u002F35686](https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FTypeScript\u002Fissues\u002F35686)。","# TypeScript Transformer 快速上手指南\n\n本指南旨在帮助开发者快速理解并编写 TypeScript 转换器（Transformer），实现对代码的自定义转换、优化或修改。\n\n## 环境准备\n\n在开始之前，请确保您的开发环境满足以下要求：\n\n*   **操作系统**：Windows, macOS 或 Linux\n*   **Node.js**：建议安装 LTS 版本 (v14+)\n*   **包管理器**：推荐使用 `yarn` 或 `npm`\n*   **前置知识**：熟悉 TypeScript 基础语法，了解抽象语法树 (AST) 基本概念\n\n## 安装步骤\n\n您可以克隆官方示例仓库来体验和学习，或者在自己的项目中集成。\n\n### 1. 获取示例项目\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fmadou\u002Ftypescript-transformer-handbook.git\ncd typescript-transformer-handbook\n```\n\n### 2. 安装依赖\n推荐使用国内镜像源加速安装（如淘宝镜像）：\n```bash\n# 使用 yarn\nyarn config set registry https:\u002F\u002Fregistry.npmmirror.com\nyarn install\n\n# 或使用 npm\nnpm config set registry https:\u002F\u002Fregistry.npmmirror.com\nnpm install\n```\n\n### 3. 构建示例\n选择您想要研究的示例进行构建：\n```bash\nyarn build example_name\n# 将 example_name 替换为具体的示例目录名\n```\n\n若要在自己的项目中启用 Transformer，通常需要安装 `ttypescript` 来替代标准的 `tsc` 编译器：\n```bash\nyarn add -D ttypescript typescript\n```\n\n## 基本使用\n\nTypeScript Transformer 本质上是一个接收 AST 节点并返回新节点的函数。以下是编写第一个简单转换器的核心步骤。\n\n### 1. 理解核心概念\n*   **AST (抽象语法树)**：代码的数据结构表示。推荐使用 [ts-ast-viewer.com](https:\u002F\u002Fts-ast-viewer.com) 在线查看代码对应的 AST 结构。\n*   **转换阶段**：\n    *   `before`: 在 TypeScript 编译前运行（最常用，用于源码转换）。\n    *   `after`: 在 TypeScript 编译后运行。\n    *   `afterDeclarations`: 在生成声明文件后运行。\n\n### 2. 编写转换器代码\n创建一个 `transformer.ts` 文件，实现一个简单的访问者模式（Visitor Pattern）：\n\n```typescript\nimport * as ts from 'typescript';\n\nfunction myTransformer(context: ts.TransformationContext): ts.TransformerFactory\u003Cts.SourceFile> {\n  return (sourceFile: ts.SourceFile) => {\n    \n    \u002F\u002F 定义访问函数\n    const visitor: ts.Visitor = (node: ts.Node) => {\n      \u002F\u002F 示例：查找所有的字符串字面量\n      if (ts.isStringLiteral(node)) {\n        \u002F\u002F 将 \"world\" 替换为 \"TypeScript\"\n        if (node.text === 'world') {\n          return ts.factory.createStringLiteral('TypeScript');\n        }\n      }\n      \n      \u002F\u002F 继续遍历子节点\n      return ts.visitEachChild(node, visitor, context);\n    };\n\n    \u002F\u002F 开始遍历\n    return ts.visitNode(sourceFile, visitor);\n  };\n}\n\nexport default myTransformer;\n```\n\n### 3. 配置并运行\n在 `tsconfig.json` 中配置 `ttypescript` 以加载您的转换器：\n\n```json\n{\n  \"compilerOptions\": {\n    \"plugins\": [\n      {\n        \"transform\": \".\u002Ftransformer.ts\",\n        \"type\": \"raw\" \n      }\n    ]\n  },\n  \"include\": [\"src\u002F**\u002F*\"]\n}\n```\n\n使用 `ttsc` 命令代替 `tsc` 进行编译以生效：\n\n```bash\nnpx ttsc\n```\n\n编译后，原本代码中的 `console.log('world')` 将被自动转换为 `console.log('TypeScript')`。","某大型前端团队在构建内部低代码平台时，需要自定义 TypeScript 编译流程，将特定的业务注解自动转换为运行时校验逻辑。\n\n### 没有 typescript-transformer-handbook 时\n- 开发者面对 TypeScript 复杂的抽象语法树（AST）文档无从下手，难以理解 Scanner、Binder 到 Transforms 的具体编译阶段。\n- 尝试手动编写转换器时，常因不懂如何正确操作 `context` 和 `visitNode`，导致节点替换失败或破坏原有的作用域绑定。\n- 缺乏系统的操作指南，在处理“变量提升”、“重命名引用”或“动态插入 Import\"等高级需求时只能盲目试错。\n- 调试过程极其痛苦，无法快速定位是遍历逻辑错误还是节点克隆方式不当，严重拖慢开发进度。\n\n### 使用 typescript-transformer-handbook 后\n- 团队通过手册清晰的“编译阶段”图解，迅速掌握了从解析到发射的全流程，精准定位代码注入的最佳时机。\n- 依据手册中关于 `visitor` 模式和 `context` 用法的最佳实践，轻松实现了安全的节点遍历与无损替换。\n- 直接复用手册提供的“转换操作”食谱（如添加导入声明、检查符号同一性），高效完成了业务注解到校验代码的自动化生成。\n- 借助手册推荐的测试库和常见陷阱提示，大幅减少了运行时报错，确保了自定义转换器在 Webpack 和 ttypescript 中的稳定运行。\n\ntypescript-transformer-handbook 将晦涩的编译器底层原理转化为可落地的实战指南，让开发者能像搭积木一样安全、高效地定制 TypeScript 编译能力。","https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fitsdouges_typescript-transformer-handbook_9ba1727a.png","itsdouges","mike douges","https:\u002F\u002Foss.gittoolsai.com\u002Favatars\u002Fitsdouges_227266a9.jpg","🏒 🇦🇺 ⚛️ 🎮 • dad x2","@paper-design","Canberra, Australia",null,"dougesdev","douges.dev","https:\u002F\u002Fgithub.com\u002Fitsdouges",[87,91,95],{"name":88,"color":89,"percentage":90},"TypeScript","#3178c6",91.1,{"name":92,"color":93,"percentage":94},"JavaScript","#f1e05a",7.7,{"name":96,"color":97,"percentage":98},"Shell","#89e051",1.2,1015,34,"2026-04-06T22:59:00","未说明","不需要 GPU",{"notes":105,"python":106,"dependencies":107},"该工具是 TypeScript 编译器插件开发指南，非独立运行的 AI 模型。运行示例需要安装 Node.js 和 Yarn 包管理器。主要依赖为 TypeScript 及其相关构建工具（如 ttypescript, webpack, parcel）。无需 GPU 或特定 Python 环境。","不需要 Python",[108,109,110,111],"typescript","ttypescript","webpack","parcel",[15],[108,114,115,116],"handbook","transformer","ts-transformer","2026-03-27T02:49:30.150509","2026-04-07T11:33:42.100139",[120,125,130,135,140,145],{"id":121,"question_zh":122,"answer_zh":123,"source_url":124},21913,"如何判断一个类是否（直接或间接）继承自另一个特定的类 X？","可以通过递归检查类的继承链来实现。利用 TypeScript 的 TypeChecker 获取基类符号，如果基类名称不匹配，则继续检查基类的基类。以下是一个示例代码片段：\n\nimport * as ts from 'typescript'\n\nconst extendsClass = (symbol: ts.Symbol, className: string, checker: ts.TypeChecker): boolean => {\n  const extendsClause = (symbol.declarations?.[0] as ts.ClassDeclaration)?.heritageClauses?.find(hc => hc.token === ts.SyntaxKind.ExtendsKeyword)\n  if (!extendsClause) {\n    return false\n  }\n  const extendsType = checker.getTypeAtLocation(extendsClause.types[0])\n  const extendsSymbol = extendsType.getSymbol()\n  if (extendsSymbol?.name === className) {\n    return true\n  }\n  return extendsSymbol ? extendsClass(extendsSymbol, className, checker) : false\n}","https:\u002F\u002Fgithub.com\u002Fitsdouges\u002Ftypescript-transformer-handbook\u002Fissues\u002F29",{"id":126,"question_zh":127,"answer_zh":128,"source_url":129},21914,"TypeScript 5.0 中 Transformer 的类型定义有什么变化导致旧代码失效？","在 TypeScript 5.0 中，transformer 函数的返回类型要求必须是 `SourceFile`，而不仅仅是 `Node`。此外，之前常用的 `ts.getMutableClone` 方法已被完全移除（此前已弃用）。升级时需要修改 transformer 以返回 SourceFile，并寻找替代 `getMutableClone` 的方案。","https:\u002F\u002Fgithub.com\u002Fitsdouges\u002Ftypescript-transformer-handbook\u002Fissues\u002F30",{"id":131,"question_zh":132,"answer_zh":133,"source_url":134},21915,"ts.create... 系列工厂函数是否已被弃用？推荐使用什么替代方案？","是的，`ts.create...`（例如 `ts.createImportClause`）系列方法已被弃用。当前版本推荐使用 `ts.factory.create...`（例如 `ts.factory.createImportClause`）来创建 AST 节点。项目维护者已在相关 PR 中更新了所有工厂函数以适配新版本。","https:\u002F\u002Fgithub.com\u002Fitsdouges\u002Ftypescript-transformer-handbook\u002Fissues\u002F28",{"id":136,"question_zh":137,"answer_zh":138,"source_url":139},21916,"ts-creator 工具现在还能用于快速生成工厂函数代码吗？","不能。ts-creator 已经过时，且已有 4 年未更新，因此不再推荐在 README 或新项目中使用。建议直接参考 TypeScript AST Viewer 提供的原生调用方式，或者手动编写工厂函数代码。","https:\u002F\u002Fgithub.com\u002Fitsdouges\u002Ftypescript-transformer-handbook\u002Fissues\u002F9",{"id":141,"question_zh":142,"answer_zh":143,"source_url":144},21917,"在 remove-node 示例中，如何正确删除节点以符合文档描述？","在运行 remove-node transformer 示例时，若要使生成的代码与文档一致，应在 visitor 函数中对需要删除的节点返回 `undefined`，而不是返回 `ts.createEmptyStatement()`。","https:\u002F\u002Fgithub.com\u002Fitsdouges\u002Ftypescript-transformer-handbook\u002Fissues\u002F10",{"id":146,"question_zh":147,"answer_zh":148,"source_url":149},21918,"能否遍历导入的节点并对其进行修改？","可以使用 `ts.visitChildren` 等方法遍历导入的节点，但这通常仅允许你读取或遍历结构，实际上无法直接通过这种方式“改变”导入的目标文件内容。如果需要调整被导入的类（如从一个文件导入的类），通常需要更复杂的逻辑来处理跨文件的符号解析和转换，单纯遍历导入节点本身不足以实现修改外部文件的需求。","https:\u002F\u002Fgithub.com\u002Fitsdouges\u002Ftypescript-transformer-handbook\u002Fissues\u002F8",[]]