{"id":18879721,"url":"https://github.com/loilo/yufka","last_synced_at":"2025-04-14T19:23:26.984Z","repository":{"id":34837078,"uuid":"184147209","full_name":"loilo/yufka","owner":"loilo","description":"🌯 Transform JavaScript ASTs the easy way","archived":false,"fork":false,"pushed_at":"2024-05-06T04:57:11.000Z","size":503,"stargazers_count":9,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T07:51:12.619Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/loilo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-04-29T21:33:51.000Z","updated_at":"2023-06-18T16:02:17.000Z","dependencies_parsed_at":"2024-05-06T05:40:27.213Z","dependency_job_id":null,"html_url":"https://github.com/loilo/yufka","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loilo%2Fyufka","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loilo%2Fyufka/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loilo%2Fyufka/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loilo%2Fyufka/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/loilo","download_url":"https://codeload.github.com/loilo/yufka/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248943509,"owners_count":21186975,"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":[],"created_at":"2024-11-08T06:39:01.591Z","updated_at":"2025-04-14T19:23:26.952Z","avatar_url":"https://github.com/loilo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"yufka.svg\" alt=\"A rolled-up Yufka with stuffing\" width=\"135\" height=\"153\"\u003e\n  \u003cbr\u003e\n  \u003ch1\u003eYufka\u003c/h1\u003e\n\u003c/div\u003e\n\nTransform JavaScript [AST](http://en.wikipedia.org/wiki/Abstract_syntax_tree)s the easy way.\n\n[![Tests](https://badgen.net/github/checks/loilo/yufka/master)](https://github.com/loilo/yufka/actions)\n[![npm](https://badgen.net/npm/v/yufka)](https://npmjs.com/package/yufka)\n\nYufka aims to be the unofficial successor to [Falafel](https://www.npmjs.com/package/falafel) and fixes most of its [outstanding issues](falafel.md#resolved-issues).\n\nSome benefits over Falafel are:\n\n* Written in TypeScript (so you get type hints in your IDE)\n* Future-proof design (no modification of third-party objects)\n* [Predictable and controllable asynchronous behavior through Promises](#asynchronous-manipulations)\n* [Creation of source maps](#source-maps)\n* Actively maintained\n\nHowever, Yufka is not a drop-in replacement for Falafel — see its [list of differences](falafel.md#differences).\n\n## Installation\n```\nnpm install --save yufka\n```\n\n## Motivation\nYufka is the ideal tool for programmatically making small \u0026 simple modifications to your JavaScript code. (For complex use cases, you probably want to head for more sophisticated solutions like [`@babel/traverse`](https://babeljs.io/docs/en/next/babel-traverse.html).)\n\nAs an introducing example, let's put a function wrapper around all array literals:\n\n```js\nconst yufka = require('yufka')\n\nconst source = `\nconst xs = [1, 2, [3, 4]]\nconst ys = [5, 6]\nconsole.log([xs, ys])\n`\n\nconst result = yufka(source, (node, { update, source }) =\u003e {\n  if (node.type === 'ArrayExpression') {\n    update(`fn(${source()})`)\n  }\n})\n\nconsole.log(result.toString())\n```\n\nOutput:\n```js\nconst xs = fn([1, 2, fn([3, 4])])\nconst ys = fn([5, 6])\nconsole.log(fn([xs, ys]))\n```\n\n## Usage\n### How it Works\n```ts\nfunction yufka(source, options = {}, manipulator)\n```\n\nTransform the string `source` with the function `manipulator`, returning an output object.\n\nFor every node in the AST, `manipulator(node, helpers)` fires. The recursive walk is an\nin-order traversal, so children get called before their parents. This makes it easier to write nested transforms since transforming parents often requires transforming their children first anyway.\n\nThe `yufka()` return value is an object with two properties:\n* `code` – contains the transformed source code\n* `map` – holds the resulting source map object, [as generated by `magic-string`](https://www.npmjs.com/package/magic-string#sgeneratemap-options-)\n\nCalling `.toString()` on a Yufka result object will return its source `code`.\n\n\u003e **Pro Tip:**\n\u003e Don't know how a JavaScript AST looks like? Have a look at [astexplorer.net](https://astexplorer.net/) to get an idea.\n\n### Options\nAll options are, as the name says, optional. If you want to provide an options object, its place is between the `source` code and the `manipulator` function.\n\n#### Acorn Options\nAny options for the underlying [`acorn`](https://npmjs.com/package/acorn) parser can be passed to `options.acorn`:\n\n```js\nyufka(source, { acorn: { sourceType: 'module' } }, (node, helpers) =\u003e {\n  // Parse the `source` as an ES module\n})\n```\n\n#### Custom Parser\nYou may pass a custom acorn parser as `options.parser` to use that\ninstead of the default acorn version coming with this library:\n\n```js\nconst acorn = require('acorn')\nconst jsx = require('acorn-jsx')\nconst parser = acorn.Parser.extend(jsx())\n\nyufka(source, { parser }, (node, helpers) =\u003e {\n  // Parse the `source` as JSX\n})\n```\n\n#### Source Maps\nYufka uses [`magic-string`](https://www.npmjs.com/package/magic-string) under the hood to generate [source maps](https://developer.mozilla.org/docs/Tools/Debugger/How_to/Use_a_source_map) for your code modifications. You can pass its [source map options](https://www.npmjs.com/package/magic-string#sgeneratemap-options-) as `options.sourceMap`:\n\n```js\nyufka(source, { sourceMap: { hires: true } }, (node, helpers) =\u003e {\n  // Create a high-resolution source map\n})\n```\n\n### Helpers\nThe `helpers` object passed to the `manipulator` function exposes the following methods. All of these methods handle the *current AST node* (the one that's passed to the manipulator as its first argument).\n\nHowever, all of these methods take an AST node as an optional first parameter if you want to access other nodes.\n\n\u003e **Example:**\n\u003e ```js\n\u003e yufka('x = 1', (node, { source }) =\u003e {\n\u003e   if (node.type === 'AssignmentExpression') {\n\u003e     // `node` refers to the `x = 1` Expression\n\u003e     source()           // returns \"x = 1\"\n\u003e     source(node.right) // returns \"1\"\n\u003e   }\n\u003e })\n\u003e ```\n\n\n#### `source()`\nReturn the source code for the given node, including any modifications made to\nchild nodes:\n\n```js\nyufka('true', (node, { source, update }) =\u003e {\n  if (node.type === 'Literal') {\n    source() // returns \"true\"\n    update('false')\n    source() // returns \"false\"\n  }\n})\n```\n\n#### `update(replacement)`\nReplace the source of the affected node with the `replacement` string:\n\n```js\nconst result = yufka('4 + 2', (node, { source, update }) =\u003e {\n  if (node.type === 'BinaryExpression') {\n    update(source(node.left) + source(node.right))\n  }\n})\n\nconsole.log(result.toString())\n```\n\nOutput:\n```js\n42\n```\n\n#### `parent(levels = 1)`\nFrom the starting node, climb up the syntax tree `levels` times. Getting an ancestor node of the program root yields `undefined`.\n\n```js\nyufka('x = [1]', (node, { parent }) =\u003e {\n  if (node.type === 'Literal') {\n    // `node` refers to the `1` literal\n    parent()  // same as parent(1), refers to the `[1]` expression\n    parent(2) // refers to the `x = [1]` assignment expression\n    parent(3) // refers to the `x = [1]` statement\n    parent(4) // refers to the program as a whole (root node)\n    parent(5) // yields `undefined`, same as parent(6), parent(7) etc.\n  }\n})\n```\n\n#### External Helper Access\n\n**Tip:** If you want to extract manipulation behavior into standalone functions, you can access the helpers directly on the `yufka` instance (e.g. `yufka.source()`) where they are not bound to a specific node:\n\n```js\n// Standalone function, increments node's value if it's a number\nconst increment = node =\u003e {\n  if (node.type === 'Literal' \u0026\u0026 typeof node.value === 'number') {\n    yufka.update(node, String(node.value + 1))\n  }\n}\n\nconst result = yufka('x = 1', node =\u003e {\n  increment(node)\n})\n\nconsole.log(result.toString())\n```\nOutput:\n```js\nx = 2\n```\n\n### Asynchronous Manipulations\nThe `manipulator` function may return a [Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise). If it does, Yufka will wait for that to resolve, making the whole `yufka()` function return a Promise resolving to the result object (instead of returning the result object directly):\n\n```js\nconst got = require('got') // see www.npmjs.com/package/got\n\nconst source = `\nconst content = curl(\"https://example.com\")\n`\nconst deferredResult = yufka(source, async (node, { source, update }) =\u003e {\n  if (node.type === 'CallExpression' \u0026\u0026 node.callee.name === 'curl') {\n    // Replace all cUrl calls with their actual content\n\n    // Get the URL (will only work for simple string literals)\n    const url = node.arguments[0].value\n\n    // Fetch the URL's contents\n    const contents = (await got(url)).body\n\n    // Replace the cUrl() call with the fetched contents\n    update(JSON.stringify(contents))\n  }\n})\n\n// Result is not available immediately, we need to await it\ndeferredResult.then(result =\u003e {\n  console.log(result.toString())\n})\n```\n\nOutput:\n```js\nconst content = \"\u003c!doctype html\u003e\\n\u003chtml\u003e\\n[...]\\n\u003c/html\u003e\"\n```\n\n\u003e **Note:** You *have* to return a promise if you want to commit updates asynchronously. Once the manipulator function is done running, any `update()` calls originating from it will throw an error.\n\n## Credit\n\nWhile the source code of this package has virtually nothing left from the [Falafel](https://www.npmjs.com/package/falafel) codebase, Yufka actually started out as a fork and its concepts stem from there.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floilo%2Fyufka","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Floilo%2Fyufka","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floilo%2Fyufka/lists"}