{"id":13660702,"url":"https://github.com/itsdouges/typescript-transformer-handbook","last_synced_at":"2025-04-12T18:49:30.020Z","repository":{"id":38432210,"uuid":"230679366","full_name":"itsdouges/typescript-transformer-handbook","owner":"itsdouges","description":"📘 A comprehensive handbook on how to create transformers for TypeScript with code examples ","archived":false,"fork":false,"pushed_at":"2023-03-31T23:14:25.000Z","size":189,"stargazers_count":941,"open_issues_count":19,"forks_count":28,"subscribers_count":16,"default_branch":"master","last_synced_at":"2024-10-29T12:57:31.289Z","etag":null,"topics":["handbook","transformer","ts-transformer","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/itsdouges.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-12-28T23:23:56.000Z","updated_at":"2024-10-29T06:11:20.000Z","dependencies_parsed_at":"2024-01-15T19:00:55.232Z","dependency_job_id":null,"html_url":"https://github.com/itsdouges/typescript-transformer-handbook","commit_stats":null,"previous_names":["madou/typescript-transformer-handbook"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itsdouges%2Ftypescript-transformer-handbook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itsdouges%2Ftypescript-transformer-handbook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itsdouges%2Ftypescript-transformer-handbook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/itsdouges%2Ftypescript-transformer-handbook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/itsdouges","download_url":"https://codeload.github.com/itsdouges/typescript-transformer-handbook/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248618218,"owners_count":21134199,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["handbook","transformer","ts-transformer","typescript"],"created_at":"2024-08-02T05:01:24.752Z","updated_at":"2025-04-12T18:49:29.997Z","avatar_url":"https://github.com/itsdouges.png","language":"TypeScript","readme":"# TypeScript Transformer Handbook\n\nThis document covers how to write a [TypeScript](https://typescriptlang.org/) [Transformer](https://basarat.gitbook.io/typescript/overview/ast).\n\n# Table of contents\n\n\u003c!-- toc --\u003e\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 \u0026 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 --\u003e\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://babeljs.io/) does it with _plugins_.\n\n\u003e Follow me [@itsmadou](https://twitter.com/itsmadou) 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 =\u003e 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\u0026 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://ts-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-\u003e SourceFile\n  -\u003e FunctionDeclaration\n      - Identifier\n  -\u003e Block\n    -\u003e ExpressionStatement\n      -\u003e CallExpression\n        -\u003e PropertyAccessExpression\n            - Identifier\n            - Identifier\n          - StringLiteral\n  - EndOfFileToken\n```\n\nFor a more detailed look check out the [AST yourself](https://ts-ast-viewer.com/#code/GYVwdgxgLglg9mABACwKYBt10QCgJSIDeAUImYhAgM5zqoB0WA5jgOQDucATugCat4A3MQC+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, // (SyntaxKind.SourceFile)\n  pos: 0,\n  end: 47,\n  statements: [{...}],\n}\n```\n\n```js\n{\n  kind: 262, // (SyntaxKind.FunctionDeclaration)\n  pos: 0,\n  end: 47,\n  name: {...},\n  body: {...},\n}\n```\n\n```js\n{\n  kind: 244, // (SyntaxKind.ExpressionStatement)\n  pos: 19,\n  end: 45,\n  expression: {...}\n}\n```\n\n\u003e `SyntaxKind` is a TypeScript enum which describes the kind of node.\n\u003e For [more information have a read of Basarat's AST tip](https://basarat.gitbook.io/typescript/overview/ast/ast-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\u003e For a more in-depth understanding of the TypeScript compiler internals have a read of [Basarat's handbook](https://basarat.gitbook.io/typescript/).\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 ~~\u003e Token Stream ~~ parser ~~\u003e 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://basarat.gitbooks.io/typescript/content/docs/compiler/parser.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://basarat.gitbooks.io/typescript/docs/compiler/scanner.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://basarat.gitbooks.io/typescript/docs/compiler/binder.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\u003e **Tip** - Type checking _should_ not happen after transforming.\n\u003e If it does it's more than likely a bug -\n\u003e 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, // (SyntaxKind.SourceFile)\n  statements: [{\n    kind: 262, // (SyntaxKind.FunctionDeclaration)\n    name: {\n      kind: 80 // (SyntaxKind.Identifier)\n      escapedText: \"hello\"\n    },\n    body: {\n      kind: 241, // (SyntaxKind.Block)\n      statements: [{\n        kind: 244, // (SyntaxKind.ExpressionStatement)\n        expression: {\n          kind: 213, // (SyntaxKind.CallExpression)\n          expression: {\n            kind: 211, // (SyntaxKind.PropertyAccessExpression)\n            name: {\n              kind: 80 // (SyntaxKind.Identifier)\n              escapedText: \"log\",\n            },\n            expression: {\n              kind: 80, // (SyntaxKind.Identifier)\n              escapedText: \"console\",\n            }\n          }\n        },\n        arguments: [{\n          kind: 11, // (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://en.wikipedia.org/wiki/Visitor_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 =\u003e {\n  const visitor = (node: ts.Node): ts.Node =\u003e {\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\u003e **Note** - You'll see that we're _returning_ each node.\n\u003e This is required!\n\u003e 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\u003e **Tip** - You can see the source for this at [/example-transformers/log-every-node](/example-transformers/log-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\u003e Most of this content is taken directly from the [Babel Handbook](https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#scopes) as the same principles apply.\n\nNext let's introduce the concept of a [scope](\u003chttps://en.wikipedia.org/wiki/Scope_(computer_science)\u003e).\nJavascript has lexical scoping ([closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)),\nwhich is a tree structure where blocks create new scope.\n\n```js\n// global scope\n\nfunction scopeOne() {\n  // scope 1\n\n  function scopeTwo() {\n    // 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; // This is a reference to a binding\n\n  function scopeTwo() {\n    ref; // 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://www.npmjs.com/package/typescript) 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\u003e **Tip** - I _strongly recommend_ using intellisense in VSCode to interrogate the API,\n\u003e 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://basarat.gitbooks.io/typescript/content/docs/compiler/binder.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\u003e = context =\u003e {\n  return sourceFile =\u003e {\n    // 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\u003e = context =\u003e {\n  return sourceFile =\u003e {\n    // 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://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-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\u003e = context =\u003e {\n  return sourceFile =\u003e {\n+    const visitor = (node: ts.Node): ts.Node =\u003e {\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\u003e = context =\u003e {\n  return sourceFile =\u003e {\n    const visitor = (node: ts.Node): ts.Node =\u003e {\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\u003e = context =\u003e {\n  return sourceFile =\u003e {\n    const visitor = (node: ts.Node): ts.Node =\u003e {\n+      if (ts.isIdentifier(node)) {\n+        // 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\u003e = context =\u003e {\n  return sourceFile =\u003e {\n    const visitor = (node: ts.Node): ts.Node =\u003e {\n      if (ts.isIdentifier(node)) {\n+        switch (node.escapedText) {\n+          case 'babel':\n+            // rename babel\n+\n+          case 'plugins':\n+            // 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\u003e = context =\u003e {\n  return sourceFile =\u003e {\n    const visitor = (node: ts.Node): ts.Node =\u003e {\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\u003e **Tip** - You can see the source for this at [/example-transformers/my-first-transformer](/example-transformers/my-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://github.com/cevek/ttypescript).\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// ts.TransformerFactory\n(context: ts.TransformationContext) =\u003e (sourceFile: ts.SourceFile) =\u003e ts.SourceFile;\n```\n\n## Config\n\nWhen your transformer needs config that can be controlled by consumers.\n\n```ts\n(config?: YourPluginConfigInterface) =\u003e 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) =\u003e 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://github.com/microsoft/TypeScript/issues/14419) 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://github.com/nonara/ts-patch)\n\n\u003e **This is the recommended approach**!\n\u003e 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\u0026 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 \u0026 methods based on the type.\n\n\u003e **Tip** - Abuse intellisense to interrogate the `ts` import for methods you can use,\n\u003e as well as [TypeScript AST Viewer](https://ts-ast-viewer.com/) to know what type a node is.\n\n```ts\nimport * as ts from 'typescript';\n\nconst visitor = (node: ts.Node): ts.Node =\u003e {\n  if (ts.isJsxAttribute(node.parent)) {\n    // node.parent is a jsx attribute\n    // ...\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; // 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) =\u003e {\n  const typeChecker = program.getTypeChecker();\n\n  // Create array of found symbols\n  const foundSymbols = new Array\u003cts.Symbol\u003e();\n\n  const transformerFactory: ts.TransformerFactory\u003cts.SourceFile\u003e = context =\u003e {\n    return sourceFile =\u003e {\n      const visitor = (node: ts.Node): ts.Node =\u003e {\n        if (ts.isIdentifier(node)) {\n          const relatedSymbol = typeChecker.getSymbolAtLocation(node);\n\n          // 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            // 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\u003e **Tip** - You can see the source for this at [/example-transformers/match-identifier-by-symbol](/example-transformers/match-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) =\u003e boolean) =\u003e {\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 =\u003e {\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\u003e **Tip** - You can see the source for this at [/example-transformers/find-parent](/example-transformers/find-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 =\u003e {\n  if (ts.isArrowFunction(node)) {\n    // 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\u003e **Tip** - You can see the source for this at [/example-transformers/update-node](/example-transformers/update-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  // 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 = () =\u003e {};\n```\n\n\u003e **Tip** - You can see the source for this at [/example-transformers/replace-node](/example-transformers/replace-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\u003e = \n  (node: TIn) =\u003e VisitResult\u003cTOut\u003e;\ntype VisitResult\u003cT extends Node | undefined\u003e = 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\u003e = context =\u003e {\n  return sourceFile =\u003e {\n    const visitor = (node: ts.Node): ts.VisitResult\u003cts.Node\u003e =\u003e {\n      // If it is a expression statement,\n      if (ts.isExpressionStatement(node)) {\n        // Return it twice.\n        // 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\u003e **Tip** - You can see the source for this at [/example-transformers/return-multiple-node](/example-transformers/return-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  // 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\u003e **Tip** - You can see the source for this at [/example-transformers/remove-node](/example-transformers/remove-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    /* modifiers */ 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  // 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\u003e **Tip** - You can see the source for this at [/example-transformers/add-import-declaration](/example-transformers/add-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) \u0026\u0026 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\u003e **Tip** - You can see the source for this at [/example-transformers/hoist-variable-declaration](/example-transformers/hoist-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\u003e **Tip** - You can see the source for this at [/example-transformers/hoist-function-declaration](/example-transformers/hoist-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\u003e **TODO** - Is this possible?\n\n### Checking if a local variable is referenced\n\n\u003e **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 /* exclamation token */,\n      undefined /* type */,\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\u003e **Tip** - You can see the source for this at [/example-transformers/create-unique-name](/example-transformers/create-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\u003e **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\u003e **TODO** - Is this possible?\n\n### Following module imports\n\nIt's possible!\n\n```ts\n// We need to use a Program transformer to get ahold of the program object.\nconst transformerProgram = (program: ts.Program) =\u003e {\n  const transformerFactory: ts.TransformerFactory\u003cts.SourceFile\u003e = context =\u003e {\n    return sourceFile =\u003e {\n      const visitor = (node: ts.Node): ts.Node =\u003e {\n        if (ts.isImportDeclaration(node) \u0026\u0026 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 =\u003e\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\u003e **Tip** - You can see the source for this at [/example-transformers/follow-imports](/example-transformers/follow-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 =\u003e {\n  if (ts.isImportDeclaration(node) \u0026\u0026 ts.isStringLiteral(node.moduleSpecifier)) {\n    // Find the import location in the file system using require.resolve\n    const pkgEntry = require.resolve(`${node.moduleSpecifier.text}`);\n\n    // Create another program\n    const innerProgram = ts.createProgram([pkgEntry], {\n      // 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\u003e **Tip** - You can see the source for this at [/example-transformers/follow-node-modules-imports](/example-transformers/follow-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://reactjs.org/docs/introducing-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 =\u003e {\n  const jsxPragma = (sourceFile as any).pragmas.get('jsx'); // 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/** @jsx jsx */\n```\n\n\u003e **Tip** - You can see the source for this at [/example-transformers/pragma-check](/example-transformers/pragma-check) - if wanting to run locally you can run it via `yarn build pragma-check`.\n\nCurrently as of 29/12/2019 `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 =\u003e {\n  sourceFile.pragmas.clear();\n  delete sourceFile.localJsxFactory;\n};\n```\n\n# Tips \u0026 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\u003e {\n  return context =\u003e {\n    const initializedTransformers = transformers.map(transformer =\u003e transformer(program)(context));\n\n    return sourceFile =\u003e {\n      return initializedTransformers.reduce((source, transformer) =\u003e {\n        return transformer(source);\n      }, sourceFile);\n    };\n  };\n}\n```\n\n## Throwing a syntax error to ease the developer experience\n\n\u003e **TODO** - Is this possible like it is in Babel?\n\u003e Or we use a [language service plugin](https://github.com/Microsoft/TypeScript/wiki/Writing-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://github.com/microsoft/TypeScript/wiki/Using-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://github.com/marionebl/ts-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://github.com/facebook/jest).\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 '../index';\nimport pkg from '../../../../package.json';\n\nconst transformer = new Transformer()\n  .addTransformer(transformerFactory)\n  .addMock({ name: pkg.name, content: `export const jsx: any = () =\u003e null` })\n  .addMock({\n    name: 'react',\n    content: `export default {} as any; export const useState = {} as any;`,\n  })\n  .setFilePath('/index.tsx');\n\nit('should add react default import if it only has named imports', () =\u003e {\n  const actual = transformer.transform(`\n    /** @jsx jsx */\n    import { useState } from 'react';\n    import { jsx } from '${pkg.name}';\n\n    \u003cdiv css={{}}\u003ehello world\u003c/div\u003e\n  `);\n\n  // 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 =\u003e {\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/closing 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://github.com/microsoft/TypeScript/issues/35686 for more information.\n","funding_links":[],"categories":["TypeScript","📦 Legacy \u0026 Inactive Projects","typescript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fitsdouges%2Ftypescript-transformer-handbook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fitsdouges%2Ftypescript-transformer-handbook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fitsdouges%2Ftypescript-transformer-handbook/lists"}