├── api ├── types-ns.md ├── types-ns.d.ts ├── printer.md ├── printer.d.ts ├── utils.d.ts ├── utils.md ├── parser.d.ts ├── parser.md ├── map-files.d.ts ├── map-files.md ├── index.d.ts ├── ee-types.d.ts ├── index.md └── ee-types.md ├── .npm-version ├── .node-version ├── .gitignore ├── src ├── types-ns.ts ├── api-urls.json ├── build-docs.ts ├── utils.ts ├── printer.ts ├── ee-types.ts ├── index.ts ├── map-files.ts ├── parser.ts └── index.test.ts ├── .npmignore ├── tsconfig.json ├── LICENSE ├── package.json └── README.md /api/types-ns.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npm-version: -------------------------------------------------------------------------------- 1 | 11.5.1 2 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | v24.6.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | -------------------------------------------------------------------------------- /api/types-ns.d.ts: -------------------------------------------------------------------------------- 1 | export * from "@babel/traverse"; 2 | export * from "@babel/types"; 3 | -------------------------------------------------------------------------------- /src/types-ns.ts: -------------------------------------------------------------------------------- 1 | export * from "@babel/traverse"; 2 | export * from "@babel/types"; 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | api 2 | src 3 | dist/build-docs.d.ts 4 | dist/build-docs.js 5 | dist/api-urls.json 6 | -------------------------------------------------------------------------------- /api/printer.md: -------------------------------------------------------------------------------- 1 | # print (exported function) 2 | 3 | Converts an AST back into a code string. 4 | 5 | This function is used internally by [transmute](/api/index.md#transmute-exported-function). 6 | 7 | The options parameter is the same type as the options parameter for `transmute`. 8 | 9 | ```ts 10 | declare function print(ast: types.Node, options?: Options): Result; 11 | ``` 12 | -------------------------------------------------------------------------------- /api/printer.d.ts: -------------------------------------------------------------------------------- 1 | import * as types from "./types-ns"; 2 | import { Result, Options } from "./ee-types"; 3 | /** 4 | * Converts an AST back into a code string. 5 | * 6 | * This function is used internally by {@link transmute}. 7 | * 8 | * The options parameter is the same type as the options parameter for `transmute`. 9 | */ 10 | export declare function print(ast: types.Node, options?: Options): Result; 11 | -------------------------------------------------------------------------------- /src/api-urls.json: -------------------------------------------------------------------------------- 1 | { 2 | "AST": "/api/ee-types.md#ast-exported-type", 3 | "parse": "/api/parser.md#parse-exported-parse", 4 | "Parse": "/api/parser.md#parse-exported-interface", 5 | "print": "/api/printer.md#print-exported-function", 6 | "transmute": "/api/index.md#transmute-exported-function", 7 | "Transmute": "/api/index.md#transmute-exported-interface", 8 | "Options": "/api/ee-types.md#options-exported-type", 9 | "Result": "/api/ee-types.md#result-exported-type", 10 | "clone": "/api/utils.md#clone-exported-function", 11 | "hasShape": "/api/utils.md#hasshape-exported-function", 12 | "mapFiles.fromPaths": "/api/map-files.md#frompaths-exported-function" 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src/**/*.ts"], 3 | "exclude": ["./**/*.test.ts"], 4 | 5 | "compilerOptions": { 6 | "lib": ["es2020", "dom"], 7 | "target": "es2018", 8 | "module": "CommonJS", 9 | "outDir": "./dist", 10 | "declaration": true, 11 | 12 | "strict": true, 13 | "noImplicitAny": false, 14 | "strictNullChecks": true, 15 | "strictFunctionTypes": true, 16 | "strictPropertyInitialization": true, 17 | "noImplicitThis": true, 18 | "alwaysStrict": true, 19 | "noUnusedLocals": false, 20 | "noUnusedParameters": false, 21 | "noImplicitReturns": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "downlevelIteration": true, 24 | 25 | "moduleResolution": "node", 26 | "esModuleInterop": true, 27 | "resolveJsonModule": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /api/utils.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility function which deeply-clones the provided object or value. Primitive 3 | * values are returned as-is (ie not cloned). 4 | * 5 | * This can be useful when you need to clone an AST node. 6 | */ 7 | export declare function clone(input: T): T; 8 | /** This type is used in the definition of `clone`, but is not exported. */ 9 | type Clonable = 10 | | {} 11 | | number 12 | | string 13 | | null 14 | | undefined 15 | | boolean 16 | | Array; 17 | /** 18 | * Utility function which checks whether `input` is a structural subset of 19 | * `shape`. 20 | * 21 | * This can be useful when you need to check if an AST node has a set of 22 | * properties. 23 | */ 24 | export declare function hasShape( 25 | input: Input, 26 | shape: Shape, 27 | ): input is Input & Shape; 28 | export {}; 29 | -------------------------------------------------------------------------------- /api/utils.md: -------------------------------------------------------------------------------- 1 | # clone (exported function) 2 | 3 | Utility function which deeply-clones the provided object or value. Primitive 4 | values are returned as-is (ie not cloned). 5 | 6 | This can be useful when you need to clone an AST node. 7 | 8 | ```ts 9 | declare function clone(input: T): T; 10 | ``` 11 | 12 | # Clonable (type) 13 | 14 | This type is used in the definition of `clone`, but is not exported. 15 | 16 | ```ts 17 | type Clonable = 18 | | {} 19 | | number 20 | | string 21 | | null 22 | | undefined 23 | | boolean 24 | | Array; 25 | ``` 26 | 27 | # hasShape (exported function) 28 | 29 | Utility function which checks whether `input` is a structural subset of 30 | `shape`. 31 | 32 | This can be useful when you need to check if an AST node has a set of 33 | properties. 34 | 35 | ```ts 36 | declare function hasShape( 37 | input: Input, 38 | shape: Shape, 39 | ): input is Input & Shape; 40 | ``` 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2022-2024 Lily Skye 2 | 3 | Permission is hereby granted, free of 4 | charge, to any person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice 12 | (including the next paragraph) shall be included in all copies or substantial 13 | portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 18 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 19 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/parser.d.ts: -------------------------------------------------------------------------------- 1 | import * as types from "./types-ns"; 2 | import { AST, Options } from "./ee-types"; 3 | /** 4 | * The various call signatures of the {@link parse} function. When option 5 | * `parseOptions.expression` is true, it returns a `types.Node`, but when it 6 | * isn't, it returns an `AST`, which is an alias for `types.File`. 7 | */ 8 | export interface Parse { 9 | (code: string): AST; 10 | ( 11 | code: string, 12 | options: Options & { 13 | parseOptions: { 14 | expression: true; 15 | }; 16 | }, 17 | ): types.Node; 18 | ( 19 | code: string, 20 | options: Options & { 21 | parseOptions: { 22 | expression: false; 23 | }; 24 | }, 25 | ): AST; 26 | ( 27 | code: string, 28 | options: Options & { 29 | parseOptions: { 30 | expression: boolean; 31 | }; 32 | }, 33 | ): types.Node; 34 | (code: string, options: Options): AST; 35 | } 36 | /** 37 | * Parses a JavaScript/TypeScript code string into an AST. 38 | * 39 | * This function is used internally by {@link transmute}. 40 | * 41 | * The options parameter is the same type as the options parameter for `transmute`. 42 | */ 43 | export declare const parse: Parse; 44 | -------------------------------------------------------------------------------- /src/build-docs.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import fsp from "node:fs/promises"; 3 | import { processSource } from "@suchipi/dtsmd"; 4 | import glomp from "glomp"; 5 | import { Path } from "nice-path"; 6 | import { run } from "clefairy"; 7 | import apiUrls from "./api-urls.json"; 8 | 9 | run({}, async function main() { 10 | const rootDir = new Path(__dirname, "..").normalize(); 11 | 12 | console.log("build-docs started"); 13 | 14 | const dtsFiles = await glomp 15 | .withExtension(".d.ts") 16 | .findMatches(rootDir.concat("api").toString()); 17 | 18 | for (const fileNameString of dtsFiles) { 19 | const dtsPath = new Path(fileNameString); 20 | 21 | console.log("processing:", dtsPath.relativeTo(rootDir).toString()); 22 | 23 | const dtsSource = await fsp.readFile(dtsPath.toString(), "utf-8"); 24 | 25 | const mdResult = await processSource(dtsSource, { 26 | fileName: fileNameString, 27 | links: apiUrls, 28 | }); 29 | for (const warning of mdResult.warnings) { 30 | console.warn(warning); 31 | } 32 | 33 | const outputPath = dtsPath.replaceLast( 34 | dtsPath.basename().replace(/\.d\.ts$/, ".md"), 35 | ); 36 | await fsp.writeFile(outputPath.toString(), mdResult.markdown); 37 | 38 | console.log("wrote:", outputPath.relativeTo(rootDir).toString()); 39 | } 40 | 41 | console.log("build-docs done"); 42 | }); 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "equivalent-exchange", 3 | "version": "3.2.0", 4 | "description": "Transmute one JavaScript string into another by way of mutating its AST. Powered by [babel](https://babeljs.io/) and [recast](https://www.npmjs.com/package/recast).", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "test": "vitest", 9 | "build": "tsc && cp dist/*.d.ts api/ && rm api/build-docs.d.ts && prettier --write api/*.d.ts && node ./dist/build-docs.js" 10 | }, 11 | "keywords": [ 12 | "ast", 13 | "transform", 14 | "codemod", 15 | "babel", 16 | "recast", 17 | "transmute", 18 | "transmutation" 19 | ], 20 | "author": "Lily Skye ", 21 | "license": "MIT", 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/suchipi/equivalent-exchange.git" 25 | }, 26 | "devDependencies": { 27 | "@suchipi/dtsmd": "^1.2.0", 28 | "@types/debug": "^4.1.12", 29 | "@types/node": "^24.3.0", 30 | "clefairy": "^2.1.0", 31 | "glomp": "^3.1.1", 32 | "nice-path": "^3.1.0", 33 | "prettier": "^3.6.2", 34 | "typescript": "^5.9.2", 35 | "vitest": "^3.2.4" 36 | }, 37 | "dependencies": { 38 | "@babel/core": "^7.28.3", 39 | "@babel/generator": "^7.28.3", 40 | "@babel/parser": "^7.28.3", 41 | "@babel/template": "^7.27.2", 42 | "@babel/traverse": "^7.28.3", 43 | "@babel/types": "^7.28.2", 44 | "@types/babel__core": "^7.20.5", 45 | "debug": "^4.4.3", 46 | "parallel-park": "^0.2.2", 47 | "recast": "^0.23.11", 48 | "tinyglobby": "^0.2.15" 49 | }, 50 | "prettier": {} 51 | } 52 | -------------------------------------------------------------------------------- /api/parser.md: -------------------------------------------------------------------------------- 1 | # Parse (exported interface) 2 | 3 | The various call signatures of the [parse](/api/parser.md#parse-exported-parse) function. When option 4 | `parseOptions.expression` is true, it returns a `types.Node`, but when it 5 | isn't, it returns an `AST`, which is an alias for `types.File`. 6 | 7 | ```ts 8 | interface Parse { 9 | (code: string): AST; 10 | ( 11 | code: string, 12 | options: Options & { 13 | parseOptions: { 14 | expression: true; 15 | }; 16 | }, 17 | ): types.Node; 18 | ( 19 | code: string, 20 | options: Options & { 21 | parseOptions: { 22 | expression: false; 23 | }; 24 | }, 25 | ): AST; 26 | ( 27 | code: string, 28 | options: Options & { 29 | parseOptions: { 30 | expression: boolean; 31 | }; 32 | }, 33 | ): types.Node; 34 | (code: string, options: Options): AST; 35 | } 36 | ``` 37 | 38 | ## Parse(...) (call signature) 39 | 40 | ```ts 41 | (code: string): AST; 42 | ``` 43 | 44 | ## Parse(...) (call signature) 45 | 46 | ```ts 47 | (code: string, options: Options & { 48 | parseOptions: { 49 | expression: true; 50 | }; 51 | }): types.Node; 52 | ``` 53 | 54 | ## Parse(...) (call signature) 55 | 56 | ```ts 57 | (code: string, options: Options & { 58 | parseOptions: { 59 | expression: false; 60 | }; 61 | }): AST; 62 | ``` 63 | 64 | ## Parse(...) (call signature) 65 | 66 | ```ts 67 | (code: string, options: Options & { 68 | parseOptions: { 69 | expression: boolean; 70 | }; 71 | }): types.Node; 72 | ``` 73 | 74 | ## Parse(...) (call signature) 75 | 76 | ```ts 77 | (code: string, options: Options): AST; 78 | ``` 79 | 80 | # parse (exported Parse) 81 | 82 | Parses a JavaScript/TypeScript code string into an AST. 83 | 84 | This function is used internally by [transmute](/api/index.md#transmute-exported-function). 85 | 86 | The options parameter is the same type as the options parameter for `transmute`. 87 | 88 | ```ts 89 | const parse: Parse; 90 | ``` 91 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility function which deeply-clones the provided object or value. Primitive 3 | * values are returned as-is (ie not cloned). 4 | * 5 | * This can be useful when you need to clone an AST node. 6 | */ 7 | export function clone(input: T): T { 8 | const cache = new Map(); 9 | return innerClone(cache, input); 10 | } 11 | 12 | /** This type is used in the definition of `clone`, but is not exported. */ 13 | type Clonable = 14 | | {} 15 | | number 16 | | string 17 | | null 18 | | undefined 19 | | boolean 20 | | Array; 21 | 22 | function innerClone(cache: Map, target: unknown): any { 23 | if (cache.has(target)) { 24 | return cache.get(target); 25 | } 26 | 27 | // primitive or function 28 | if (typeof target !== "object" || target == null) { 29 | return target; 30 | } 31 | 32 | if (Array.isArray(target)) { 33 | const copy = new Array(target.length); 34 | cache.set(target, copy); 35 | for (let i = 0; i < target.length; i++) { 36 | copy[i] = innerClone(cache, target[i]); 37 | cache.set(target[i], copy[i]); 38 | } 39 | // @ts-ignore could be instantiated with different subtype 40 | return copy; 41 | } 42 | 43 | const copy = Object.create(Object.getPrototypeOf(target)); 44 | cache.set(target, copy); 45 | for (const key of Object.keys(target)) { 46 | copy[key] = innerClone(cache, target[key]); 47 | cache.set(target[key], copy[key]); 48 | } 49 | 50 | return copy; 51 | } 52 | 53 | /** 54 | * Utility function which checks whether `input` is a structural subset of 55 | * `shape`. 56 | * 57 | * This can be useful when you need to check if an AST node has a set of 58 | * properties. 59 | */ 60 | export function hasShape( 61 | input: Input, 62 | shape: Shape, 63 | ): input is Input & Shape { 64 | if (typeof input !== "object" || input == null) { 65 | // @ts-ignore no overlap 66 | return input === shape; 67 | } 68 | 69 | // Used for both Array and Object 70 | return Object.keys(shape as any).every((key) => 71 | hasShape(input[key], shape[key]), 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /src/printer.ts: -------------------------------------------------------------------------------- 1 | import * as babelGenerator from "@babel/generator"; 2 | import * as types from "./types-ns"; 3 | import * as recast from "recast"; 4 | import { Result, Options } from "./ee-types"; 5 | import makeDebug from "debug"; 6 | 7 | const debug = makeDebug("equivalent-exchange:printer"); 8 | 9 | /** 10 | * Converts an AST back into a code string. 11 | * 12 | * This function is used internally by {@link transmute}. 13 | * 14 | * The options parameter is the same type as the options parameter for `transmute`. 15 | */ 16 | export function print(ast: types.Node, options?: Options): Result { 17 | debug("print", { ast, options }); 18 | if (options) { 19 | const maybeWrongOpts = options as any; 20 | if ("printMethod" in maybeWrongOpts) { 21 | throw new Error( 22 | "`print` function received a legacy PrintOptions, but we want an Options. The following property should be in a sub-object under `printOptions`: printMethod", 23 | ); 24 | } 25 | } 26 | 27 | const printMethod = options?.printOptions?.printMethod || "recast.print"; 28 | 29 | debug("printing using", printMethod); 30 | 31 | switch (printMethod) { 32 | case "recast.print": 33 | case "recast.prettyPrint": { 34 | const printFunction = 35 | printMethod === "recast.print" ? recast.print : recast.prettyPrint; 36 | 37 | if (options?.fileName && options.sourceMapFileName) { 38 | const { code, map } = printFunction.call(recast, ast, { 39 | sourceFileName: options.fileName, 40 | sourceMapName: options.sourceMapFileName, 41 | }); 42 | debug("returning with sourcemap maybe"); 43 | return { code, map: map || null }; 44 | } else { 45 | const { code } = printFunction.call(recast, ast); 46 | debug("returning without sourcemap"); 47 | return { code, map: null }; 48 | } 49 | } 50 | 51 | case "@babel/generator": { 52 | const { code, map } = babelGenerator.default(ast, { 53 | sourceFileName: options?.fileName, 54 | sourceMaps: Boolean(options?.sourceMapFileName), 55 | }); 56 | 57 | if (options?.fileName && options.sourceMapFileName) { 58 | debug("returning with sourcemap maybe"); 59 | return { code, map: map || null }; 60 | } else { 61 | debug("returning without sourcemap"); 62 | return { code, map: null }; 63 | } 64 | } 65 | 66 | default: { 67 | throw new Error(`Invalid print method: ${printMethod}`); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /api/map-files.d.ts: -------------------------------------------------------------------------------- 1 | import tinyglobby = require("tinyglobby"); 2 | import { Result } from "./ee-types"; 3 | export type FileStatus = 4 | | { 5 | status: "unchanged"; 6 | } 7 | | { 8 | status: "changed"; 9 | before: string; 10 | after: string; 11 | } 12 | | { 13 | status: "errored"; 14 | error: Error; 15 | }; 16 | export type FileResults = Map; 17 | export type TransformFunction = ( 18 | fileContent: string, 19 | filePath: string, 20 | ) => TransformReturn; 21 | export type TransformReturn = 22 | | string 23 | | void 24 | | Result 25 | | Promise; 26 | /** 27 | * Helper function which applies a transform function to a set of files, 28 | * specified via glob patterns. 29 | * 30 | * Each file's content will be read as a UTF-8 string and then provided to the 31 | * transform function. If the transform function returns a *different* UTF-8 32 | * string, the file will be updated such that its content is equivalent to the 33 | * string returned from the transform function. 34 | * 35 | * Transform functions can return `undefined` to skip updating a file. 36 | * 37 | * The return value of `fromGlob` is a Map whose keys are filepath strings and 38 | * whose value is one of the following: 39 | * 40 | * - `{ status: "unchanged" }`, which indicates the file was not modified 41 | * - `{ status: "changed", before: string, after: string }`, which indicates the file WAS modified 42 | * - `{ status: "errored", error: Error }`, which indicates that the file could not be processed due to an error. 43 | */ 44 | export declare function fromGlob( 45 | patterns: string | Array, 46 | transform: TransformFunction, 47 | ): Promise; 48 | export declare function fromGlob( 49 | patterns: string | Array, 50 | globOptions: tinyglobby.GlobOptions | undefined, 51 | transform: TransformFunction, 52 | ): Promise; 53 | /** 54 | * Helper function which applies a transform function to a set of files, 55 | * specified via an array of filepath strings. 56 | * 57 | * Each file's content will be read as a UTF-8 string and then provided to the 58 | * transform function. If the transform function returns a *different* UTF-8 59 | * string, the file will be updated such that its content is equivalent to the 60 | * string returned from the transform function. 61 | * 62 | * Transform functions can return `undefined` to skip updating a file. 63 | * 64 | * The return value of `fromGlob` is a Map whose keys are filepath strings and 65 | * whose value is one of the following: 66 | * 67 | * - `{ status: "unchanged" }`, which indicates the file was not modified 68 | * - `{ status: "changed", before: string, after: string }`, which indicates the file WAS modified 69 | * - `{ status: "errored", error: Error }`, which indicates that the file could not be processed due to an error. 70 | */ 71 | export declare function fromPaths( 72 | filePaths: Array, 73 | transform: TransformFunction, 74 | ): Promise; 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # equivalent-exchange 2 | 3 | Suchipi's flexible JS/TS codemodding/refactoring toolkit, powered by [Babel](https://babeljs.io/) and [Recast](https://www.npmjs.com/package/recast). 4 | 5 | ## Features 6 | 7 | - Can parse code using modern ES20XX syntax, as well as JSX/TSX and TypeScript/Flow syntax. 8 | - Maintains the source formatting of the original source, where possible; only modified parts of the code will be touched. 9 | - Can generate a source map that maps your input file into your transformed output. 10 | 11 | ## Usage Example 12 | 13 | "Transmute" one string of code into another by using the `transmute` function: 14 | 15 | ```ts 16 | import { transmute } from "equivalent-exchange"; 17 | 18 | const someJs = "console.log('hi!');"; 19 | 20 | const result = transmute(someJs, (ast) => { 21 | // Within this callback, we mutate the AST as desired for the 22 | // codemod/refactor. 23 | // Use https://astexplorer.net/ to see what this tree 24 | // structure looks like! 25 | ast.program.body[0].expression.callee.arguments[0].value = "goodbye!"; 26 | }); 27 | 28 | console.log(result.code); // console.log("goodbye!"); 29 | ``` 30 | 31 | For more flexible codemods, use the included utilities: 32 | 33 | ```ts 34 | import { transmute, traverse, types } from "equivalent-exchange"; 35 | 36 | // `traverse` and `types` come from Babel! 37 | 38 | const someJs = "console.log('hi!', 'hi again!');"; 39 | 40 | const result = transmute(someJs, (ast) => { 41 | // Walk the tree... 42 | traverse(ast, { 43 | // And for every StringLiteral node we find... 44 | StringLiteral(path) { 45 | const { node } = path; 46 | // If it starts with 'hi'... 47 | if (node.value.startsWith("hi")) { 48 | const newValue = node.value.replace(/^hi/, "bye"); 49 | const newNode = types.stringLiteral(newValue); 50 | // Change 'hi' to 'bye' 51 | path.replaceWith(newNode); 52 | } 53 | }, 54 | }); 55 | }); 56 | 57 | console.log(result.code); // "console.log('bye!', 'bye again!');" 58 | ``` 59 | 60 | To transform files in bulk, use the "mapFiles" API: 61 | 62 | ```ts 63 | import { transmute, traverse, types, mapFiles } from "equivalent-exchange"; 64 | 65 | const results = await mapFiles.fromGlob("src/*.ts", (content, path) => { 66 | if (path === "src/build-docs.ts") { 67 | // Skip this file 68 | return; 69 | } 70 | 71 | return transmute(content, (ast) => { 72 | // Walk the tree... 73 | traverse(ast, { 74 | // And for every StringLiteral node we find... 75 | StringLiteral(path) { 76 | const { node } = path; 77 | // If it starts with 'hi'... 78 | if (node.value.startsWith("hi")) { 79 | const newValue = node.value.replace(/^hi/, "bye"); 80 | const newNode = types.stringLiteral(newValue); 81 | // Change 'hi' to 'bye' 82 | path.replaceWith(newNode); 83 | } 84 | }, 85 | }); 86 | }); 87 | }); 88 | ``` 89 | 90 | ## API Documentation 91 | 92 | See [api/index.md](/api/index.md). 93 | 94 | ## License 95 | 96 | MIT 97 | -------------------------------------------------------------------------------- /api/map-files.md: -------------------------------------------------------------------------------- 1 | # FileStatus (exported type) 2 | 3 | ```ts 4 | type FileStatus = 5 | | { 6 | status: "unchanged"; 7 | } 8 | | { 9 | status: "changed"; 10 | before: string; 11 | after: string; 12 | } 13 | | { 14 | status: "errored"; 15 | error: Error; 16 | }; 17 | ``` 18 | 19 | # FileResults (exported type) 20 | 21 | ```ts 22 | type FileResults = Map; 23 | ``` 24 | 25 | # TransformFunction (exported type) 26 | 27 | ```ts 28 | type TransformFunction = ( 29 | fileContent: string, 30 | filePath: string, 31 | ) => TransformReturn; 32 | ``` 33 | 34 | # TransformReturn (exported type) 35 | 36 | ```ts 37 | type TransformReturn = string | void | Result | Promise; 38 | ``` 39 | 40 | # fromGlob (exported function) 41 | 42 | Helper function which applies a transform function to a set of files, 43 | specified via glob patterns. 44 | 45 | Each file's content will be read as a UTF-8 string and then provided to the 46 | transform function. If the transform function returns a _different_ UTF-8 47 | string, the file will be updated such that its content is equivalent to the 48 | string returned from the transform function. 49 | 50 | Transform functions can return `undefined` to skip updating a file. 51 | 52 | The return value of `fromGlob` is a Map whose keys are filepath strings and 53 | whose value is one of the following: 54 | 55 | - `{ status: "unchanged" }`, which indicates the file was not modified 56 | - `{ status: "changed", before: string, after: string }`, which indicates the file WAS modified 57 | - `{ status: "errored", error: Error }`, which indicates that the file could not be processed due to an error. 58 | 59 | ```ts 60 | declare function fromGlob( 61 | patterns: string | Array, 62 | transform: TransformFunction, 63 | ): Promise; 64 | ``` 65 | 66 | # fromGlob (exported function) 67 | 68 | ```ts 69 | declare function fromGlob( 70 | patterns: string | Array, 71 | globOptions: tinyglobby.GlobOptions | undefined, 72 | transform: TransformFunction, 73 | ): Promise; 74 | ``` 75 | 76 | # fromPaths (exported function) 77 | 78 | Helper function which applies a transform function to a set of files, 79 | specified via an array of filepath strings. 80 | 81 | Each file's content will be read as a UTF-8 string and then provided to the 82 | transform function. If the transform function returns a _different_ UTF-8 83 | string, the file will be updated such that its content is equivalent to the 84 | string returned from the transform function. 85 | 86 | Transform functions can return `undefined` to skip updating a file. 87 | 88 | The return value of `fromGlob` is a Map whose keys are filepath strings and 89 | whose value is one of the following: 90 | 91 | - `{ status: "unchanged" }`, which indicates the file was not modified 92 | - `{ status: "changed", before: string, after: string }`, which indicates the file WAS modified 93 | - `{ status: "errored", error: Error }`, which indicates that the file could not be processed due to an error. 94 | 95 | ```ts 96 | declare function fromPaths( 97 | filePaths: Array, 98 | transform: TransformFunction, 99 | ): Promise; 100 | ``` 101 | -------------------------------------------------------------------------------- /api/index.d.ts: -------------------------------------------------------------------------------- 1 | import traverse from "@babel/traverse"; 2 | import template from "@babel/template"; 3 | import * as types from "./types-ns"; 4 | import { AST, Options, Result } from "./ee-types"; 5 | import { parse, Parse } from "./parser"; 6 | import { print } from "./printer"; 7 | import { clone, hasShape } from "./utils"; 8 | import * as mapFiles from "./map-files"; 9 | /** 10 | * The transmute function; star of the library. See {@link Transmute}. 11 | */ 12 | export declare const transmute: Transmute; 13 | /** 14 | * The interface of the `transmute` function, which has 4 different call signatures. 15 | */ 16 | export interface Transmute { 17 | /** 18 | * Parses `code` into an AST, then passes that to `transform`, which is 19 | * expected to mutate the AST somehow. 20 | * 21 | * Once the Promise returned by `transform` has resolved, it converts the AST 22 | * back into a string, and returns you a {@link Result}, which has 23 | * the transformed string on it as its `code` property. 24 | */ 25 | (code: string, transform: (ast: AST) => Promise): Promise; 26 | /** 27 | * Parses `code` into an AST, then passes that to `transform`, which is 28 | * expected to mutate the AST somehow. 29 | * 30 | * Once the Promise returned by `transform` has resolved, it converts the AST 31 | * back into a string, and returns you a {@link Result}, which has 32 | * the transformed string on it as its `code` property. 33 | * 34 | * The contents of `options` will determine what syntax options to use to 35 | * parse the code, and whether to consume/generate source maps. See 36 | * {@link Options} for more details. 37 | */ 38 | ( 39 | code: string, 40 | options: Options, 41 | transform: (ast: types.Node) => Promise, 42 | ): Promise; 43 | /** 44 | * Parses `code` into an AST, then passes that to `transform`, which 45 | * is expected to mutate the AST somehow. 46 | * 47 | * Then, it converts the AST back into a string, and returns you a 48 | * {@link Result}, which has the transformed string on it as its 49 | * `code` property. 50 | */ 51 | (code: string, transform: (ast: AST) => void): Result; 52 | /** 53 | * Parses `code` into an AST, then passes that to `transform`, which is 54 | * expected to mutate the AST somehow. 55 | * 56 | * Then, it converts the AST back into a string, and returns you a 57 | * {@link Result}, which has the transformed string on it as its 58 | * `code` property. 59 | * 60 | * The contents of `options` will determine what syntax options to use to 61 | * parse the code, and whether to consume/generate source maps. See 62 | * {@link Options} for more details. 63 | */ 64 | ( 65 | code: string, 66 | options: Options, 67 | transform: (ast: types.Node) => void, 68 | ): Result; 69 | } 70 | export { 71 | /** 72 | * Parser (code-to-AST) function used by `transmute`. See {@link parse}. 73 | */ 74 | parse, 75 | /** 76 | * Type of the `parse` function. See {@link Parse}. 77 | */ 78 | Parse, 79 | /** 80 | * Printer (AST-to-code) function used by `transmute`. See {@link print}. 81 | */ 82 | print, 83 | /** 84 | * Re-export of `@babel/traverse`'s default export. 85 | */ 86 | traverse, 87 | /** 88 | * Contains the named exports of both `@babel/types` and `@babel/traverse`. 89 | */ 90 | types, 91 | /** 92 | * Re-export of `@babel/template`'s default export. 93 | */ 94 | template, 95 | /** 96 | * Type returned by {@link parse}. See {@link AST}. 97 | */ 98 | type AST, 99 | /** 100 | * Type used by {@link transmute}, {@link parse}, and {@link print}. See 101 | * {@link Options}. 102 | */ 103 | type Options, 104 | /** 105 | * Type returned by {@link print}. See {@link Result}. 106 | */ 107 | type Result, 108 | /** 109 | * AST node cloner utility function. See {@link clone}. 110 | */ 111 | clone, 112 | /** 113 | * Deep object property comparison checker utility function. See 114 | * {@link hasShape}. 115 | */ 116 | hasShape, 117 | /** 118 | * Bulk file content transform utility functions. See {@link mapFiles.fromPaths}. 119 | */ 120 | mapFiles, 121 | }; 122 | -------------------------------------------------------------------------------- /api/ee-types.d.ts: -------------------------------------------------------------------------------- 1 | import * as types from "./types-ns"; 2 | /** 3 | * The node type that `transmute`/`parse` will give to you as the root AST node. 4 | * 5 | * It's an alias to babel's `File` node type. 6 | */ 7 | export type AST = types.File; 8 | /** 9 | * Options type used by `transmute`, `parse`, and `print`. 10 | */ 11 | export type Options = { 12 | /** 13 | * The name of the file whose code you are transmuting. 14 | * 15 | * If passed, parse errors thrown by `transmute` will be clearer, and it will 16 | * be possible to generate sourcemaps for the file. 17 | */ 18 | fileName?: string; 19 | /** 20 | * The name of the output file where you will store a sourcemap generated by 21 | * transmute. 22 | * 23 | * Both `fileName` and `sourceMapFileName` must be present to generate a 24 | * sourcemap. 25 | */ 26 | sourceMapFileName?: string; 27 | /** 28 | * An input sourcemap to compose the newly-generated sourcemap with. 29 | */ 30 | inputSourceMap?: any; 31 | /** 32 | * Options that control how code strings will be converted into ASTs. 33 | */ 34 | parseOptions?: { 35 | /** 36 | * Which type-checker syntax to use. 37 | * 38 | * Defaults to "typescript". 39 | */ 40 | typeSyntax?: "typescript" | "typescript-dts" | "flow"; 41 | /** 42 | * Which decorator proposal syntax to use. 43 | * 44 | * Defaults to "legacy". 45 | */ 46 | decoratorSyntax?: "new" | "legacy"; 47 | /** 48 | * Which syntax proposal to use for the pipeline operator. 49 | * 50 | * Defaults to "hack". 51 | */ 52 | pipelineSyntax?: "minimal" | "fsharp" | "hack" | "smart" | "none"; 53 | /** 54 | * Which topic token to use when using the "hack" syntax proposal for the 55 | * pipeline operator. 56 | * 57 | * Defaults to "%". Only used when pipelineSyntax is "hack". 58 | */ 59 | hackPipelineTopicToken?: "^^" | "@@" | "^" | "%" | "#"; 60 | /** 61 | * Whether to parse matching < and > in the code as JSX. It is generally 62 | * okay to have this on, but if your code is not JSX, and you do some nested 63 | * numeric comparisons, or use < and > for type casting, you should disable 64 | * this. 65 | * 66 | * Defaults to `true`. 67 | */ 68 | jsxEnabled?: boolean; 69 | /** 70 | * Whether to enable `@babel/parser`'s `v8intrinsic` plugin, which allows 71 | * parsing V8 identifier syntax like `%GetOptimizationStatus(fn)`. 72 | * 73 | * NOTE: If you enable this, you CANNOT set pipelineSyntax to "hack" (which 74 | * is the default). 75 | */ 76 | v8Intrinsic?: boolean; 77 | /** 78 | * Whether to enable `@babel/parser`'s `placeholders` plugin, which allows 79 | * parsing syntax like `const thing = %%SOMETHING%%`. 80 | * 81 | * NOTE: If you enable this, you CANNOT set pipelineSyntax to "hack" (which 82 | * is the default). 83 | */ 84 | placeholders?: boolean; 85 | /** 86 | * Whether to parse the code as an expression instead of a whole program. 87 | * 88 | * When this is true, the type of the resulting AST node will vary. 89 | * 90 | * Defaults to false. 91 | */ 92 | expression?: boolean; 93 | /** 94 | * Whether to wrap the resulting Babel AST with Recast, to keep track of 95 | * changes so that code can be re-printed with minimal changes. 96 | * 97 | * Setting this to true reduces cpu clocktime and gc allocations, at the 98 | * expense of not preserving source code formatting for non-modified parts 99 | * of the AST. 100 | * 101 | * It might make sense to set this to true if you're going to change 102 | * `printOptions.printMethod` from the default value. 103 | * 104 | * Defaults to false. 105 | */ 106 | skipRecast?: boolean; 107 | }; 108 | /** 109 | * Options that control how ASTs will be converted into code strings. 110 | */ 111 | printOptions?: { 112 | /** 113 | * Which method to use to convert an AST back into code. 114 | * 115 | * Defaults to "recast.print", which attempts to preserve source formatting 116 | * of unchanged nodes. If this doesn't matter to you, you can instead use 117 | * "recast.prettyPrint", which reprints all nodes with generic formatting. 118 | * 119 | * "@babel/generator" will use the npm package "@babel/generator" to make 120 | * the code instead. If you encounter bugs with recast's printer, babel 121 | * generator may work better. 122 | */ 123 | printMethod?: "recast.print" | "recast.prettyPrint" | "@babel/generator"; 124 | }; 125 | }; 126 | /** 127 | * The result of transmuting some code. If the options you passed into 128 | * `transmute` had sourcemap-related stuff set, then the `map` property 129 | * will be set. Otherwise, it'll be null. 130 | */ 131 | export type Result = { 132 | /** 133 | * The transformed code string. 134 | */ 135 | code: string; 136 | /** 137 | * A source map describing the changes between the original code string and 138 | * its transformed version. 139 | */ 140 | map?: any; 141 | }; 142 | -------------------------------------------------------------------------------- /src/ee-types.ts: -------------------------------------------------------------------------------- 1 | import * as types from "./types-ns"; 2 | 3 | /** 4 | * The node type that `transmute`/`parse` will give to you as the root AST node. 5 | * 6 | * It's an alias to babel's `File` node type. 7 | */ 8 | export type AST = types.File; 9 | 10 | /** 11 | * Options type used by `transmute`, `parse`, and `print`. 12 | */ 13 | export type Options = { 14 | /** 15 | * The name of the file whose code you are transmuting. 16 | * 17 | * If passed, parse errors thrown by `transmute` will be clearer, and it will 18 | * be possible to generate sourcemaps for the file. 19 | */ 20 | fileName?: string; 21 | 22 | /** 23 | * The name of the output file where you will store a sourcemap generated by 24 | * transmute. 25 | * 26 | * Both `fileName` and `sourceMapFileName` must be present to generate a 27 | * sourcemap. 28 | */ 29 | sourceMapFileName?: string; 30 | 31 | /** 32 | * An input sourcemap to compose the newly-generated sourcemap with. 33 | */ 34 | inputSourceMap?: any; 35 | 36 | /** 37 | * Options that control how code strings will be converted into ASTs. 38 | */ 39 | parseOptions?: { 40 | /** 41 | * Which type-checker syntax to use. 42 | * 43 | * Defaults to "typescript". 44 | */ 45 | typeSyntax?: "typescript" | "typescript-dts" | "flow"; 46 | 47 | /** 48 | * Which decorator proposal syntax to use. 49 | * 50 | * Defaults to "legacy". 51 | */ 52 | decoratorSyntax?: "new" | "legacy"; 53 | 54 | /** 55 | * Which syntax proposal to use for the pipeline operator. 56 | * 57 | * Defaults to "hack". 58 | */ 59 | pipelineSyntax?: "minimal" | "fsharp" | "hack" | "smart" | "none"; 60 | 61 | /** 62 | * Which topic token to use when using the "hack" syntax proposal for the 63 | * pipeline operator. 64 | * 65 | * Defaults to "%". Only used when pipelineSyntax is "hack". 66 | */ 67 | hackPipelineTopicToken?: "^^" | "@@" | "^" | "%" | "#"; 68 | 69 | /** 70 | * Whether to parse matching < and > in the code as JSX. It is generally 71 | * okay to have this on, but if your code is not JSX, and you do some nested 72 | * numeric comparisons, or use < and > for type casting, you should disable 73 | * this. 74 | * 75 | * Defaults to `true`. 76 | */ 77 | jsxEnabled?: boolean; 78 | 79 | /** 80 | * Whether to enable `@babel/parser`'s `v8intrinsic` plugin, which allows 81 | * parsing V8 identifier syntax like `%GetOptimizationStatus(fn)`. 82 | * 83 | * NOTE: If you enable this, you CANNOT set pipelineSyntax to "hack" (which 84 | * is the default). 85 | */ 86 | v8Intrinsic?: boolean; 87 | 88 | /** 89 | * Whether to enable `@babel/parser`'s `placeholders` plugin, which allows 90 | * parsing syntax like `const thing = %%SOMETHING%%`. 91 | * 92 | * NOTE: If you enable this, you CANNOT set pipelineSyntax to "hack" (which 93 | * is the default). 94 | */ 95 | placeholders?: boolean; 96 | 97 | /** 98 | * Whether to parse the code as an expression instead of a whole program. 99 | * 100 | * When this is true, the type of the resulting AST node will vary. 101 | * 102 | * Defaults to false. 103 | */ 104 | expression?: boolean; 105 | 106 | /** 107 | * Whether to wrap the resulting Babel AST with Recast, to keep track of 108 | * changes so that code can be re-printed with minimal changes. 109 | * 110 | * Setting this to true reduces cpu clocktime and gc allocations, at the 111 | * expense of not preserving source code formatting for non-modified parts 112 | * of the AST. 113 | * 114 | * It might make sense to set this to true if you're going to change 115 | * `printOptions.printMethod` from the default value. 116 | * 117 | * Defaults to false. 118 | */ 119 | skipRecast?: boolean; 120 | }; 121 | 122 | /** 123 | * Options that control how ASTs will be converted into code strings. 124 | */ 125 | printOptions?: { 126 | /** 127 | * Which method to use to convert an AST back into code. 128 | * 129 | * Defaults to "recast.print", which attempts to preserve source formatting 130 | * of unchanged nodes. If this doesn't matter to you, you can instead use 131 | * "recast.prettyPrint", which reprints all nodes with generic formatting. 132 | * 133 | * "@babel/generator" will use the npm package "@babel/generator" to make 134 | * the code instead. If you encounter bugs with recast's printer, babel 135 | * generator may work better. 136 | */ 137 | printMethod?: "recast.print" | "recast.prettyPrint" | "@babel/generator"; 138 | }; 139 | }; 140 | 141 | /** 142 | * The result of transmuting some code. If the options you passed into 143 | * `transmute` had sourcemap-related stuff set, then the `map` property 144 | * will be set. Otherwise, it'll be null. 145 | */ 146 | export type Result = { 147 | /** 148 | * The transformed code string. 149 | */ 150 | code: string; 151 | 152 | /** 153 | * A source map describing the changes between the original code string and 154 | * its transformed version. 155 | */ 156 | map?: any; 157 | }; 158 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import traverse from "@babel/traverse"; 2 | import template from "@babel/template"; 3 | import * as types from "./types-ns"; 4 | import { AST, Options, Result } from "./ee-types"; 5 | import { parse, Parse } from "./parser"; 6 | import { print } from "./printer"; 7 | import { clone, hasShape } from "./utils"; 8 | import * as mapFiles from "./map-files"; 9 | import makeDebug from "debug"; 10 | 11 | const debugTransmute = makeDebug("equivalent-exchange:transmute"); 12 | 13 | /** 14 | * The transmute function; star of the library. See {@link Transmute}. 15 | */ 16 | // @ts-ignore typescript overload refinement leaves a lot to be desired 17 | export const transmute: Transmute = ( 18 | ...args: Array 19 | ): Result | Promise => { 20 | const code: string = args[0]; 21 | let options: Options = {}; 22 | let transform: (ast: types.Node) => void | Promise; 23 | if (typeof args[1] === "function") { 24 | transform = args[1]; 25 | } else { 26 | options = args[1]; 27 | transform = args[2]; 28 | } 29 | 30 | debugTransmute("Parsing..."); 31 | const ast = parse(code, options); 32 | 33 | debugTransmute("Running transform..."); 34 | const result = transform(ast); 35 | if ( 36 | typeof result === "object" && 37 | result != null && 38 | typeof result.then === "function" 39 | ) { 40 | debugTransmute("Transform returned Promise. Awaiting Promise..."); 41 | return result.then(() => { 42 | debugTransmute("Printing..."); 43 | return print(ast, options); 44 | }); 45 | } else { 46 | debugTransmute("Printing..."); 47 | return print(ast, options); 48 | } 49 | }; 50 | 51 | /** 52 | * The interface of the `transmute` function, which has 4 different call signatures. 53 | */ 54 | export interface Transmute { 55 | /** 56 | * Parses `code` into an AST, then passes that to `transform`, which is 57 | * expected to mutate the AST somehow. 58 | * 59 | * Once the Promise returned by `transform` has resolved, it converts the AST 60 | * back into a string, and returns you a {@link Result}, which has 61 | * the transformed string on it as its `code` property. 62 | */ 63 | (code: string, transform: (ast: AST) => Promise): Promise; 64 | 65 | /** 66 | * Parses `code` into an AST, then passes that to `transform`, which is 67 | * expected to mutate the AST somehow. 68 | * 69 | * Once the Promise returned by `transform` has resolved, it converts the AST 70 | * back into a string, and returns you a {@link Result}, which has 71 | * the transformed string on it as its `code` property. 72 | * 73 | * The contents of `options` will determine what syntax options to use to 74 | * parse the code, and whether to consume/generate source maps. See 75 | * {@link Options} for more details. 76 | */ 77 | ( 78 | code: string, 79 | options: Options, 80 | transform: (ast: types.Node) => Promise, 81 | ): Promise; 82 | 83 | /** 84 | * Parses `code` into an AST, then passes that to `transform`, which 85 | * is expected to mutate the AST somehow. 86 | * 87 | * Then, it converts the AST back into a string, and returns you a 88 | * {@link Result}, which has the transformed string on it as its 89 | * `code` property. 90 | */ 91 | (code: string, transform: (ast: AST) => void): Result; 92 | 93 | /** 94 | * Parses `code` into an AST, then passes that to `transform`, which is 95 | * expected to mutate the AST somehow. 96 | * 97 | * Then, it converts the AST back into a string, and returns you a 98 | * {@link Result}, which has the transformed string on it as its 99 | * `code` property. 100 | * 101 | * The contents of `options` will determine what syntax options to use to 102 | * parse the code, and whether to consume/generate source maps. See 103 | * {@link Options} for more details. 104 | */ 105 | ( 106 | code: string, 107 | options: Options, 108 | transform: (ast: types.Node) => void, 109 | ): Result; 110 | } 111 | 112 | export { 113 | /** 114 | * Parser (code-to-AST) function used by `transmute`. See {@link parse}. 115 | */ 116 | parse, 117 | 118 | /** 119 | * Type of the `parse` function. See {@link Parse}. 120 | */ 121 | Parse, 122 | 123 | /** 124 | * Printer (AST-to-code) function used by `transmute`. See {@link print}. 125 | */ 126 | print, 127 | 128 | /** 129 | * Re-export of `@babel/traverse`'s default export. 130 | */ 131 | traverse, 132 | 133 | /** 134 | * Contains the named exports of both `@babel/types` and `@babel/traverse`. 135 | */ 136 | types, 137 | 138 | /** 139 | * Re-export of `@babel/template`'s default export. 140 | */ 141 | template, 142 | 143 | /** 144 | * Type returned by {@link parse}. See {@link AST}. 145 | */ 146 | type AST, 147 | 148 | /** 149 | * Type used by {@link transmute}, {@link parse}, and {@link print}. See 150 | * {@link Options}. 151 | */ 152 | type Options, 153 | 154 | /** 155 | * Type returned by {@link print}. See {@link Result}. 156 | */ 157 | type Result, 158 | 159 | /** 160 | * AST node cloner utility function. See {@link clone}. 161 | */ 162 | clone, 163 | 164 | /** 165 | * Deep object property comparison checker utility function. See 166 | * {@link hasShape}. 167 | */ 168 | hasShape, 169 | 170 | /** 171 | * Bulk file content transform utility functions. See {@link mapFiles.fromPaths}. 172 | */ 173 | mapFiles, 174 | }; 175 | -------------------------------------------------------------------------------- /api/index.md: -------------------------------------------------------------------------------- 1 | # transmute (exported Transmute) 2 | 3 | The transmute function; star of the library. See [Transmute](/api/index.md#transmute-exported-interface). 4 | 5 | ```ts 6 | const transmute: Transmute; 7 | ``` 8 | 9 | # Transmute (exported interface) 10 | 11 | The interface of the `transmute` function, which has 4 different call signatures. 12 | 13 | ```ts 14 | interface Transmute { 15 | (code: string, transform: (ast: AST) => Promise): Promise; 16 | ( 17 | code: string, 18 | options: Options, 19 | transform: (ast: types.Node) => Promise, 20 | ): Promise; 21 | (code: string, transform: (ast: AST) => void): Result; 22 | ( 23 | code: string, 24 | options: Options, 25 | transform: (ast: types.Node) => void, 26 | ): Result; 27 | } 28 | ``` 29 | 30 | ## Transmute(...) (call signature) 31 | 32 | Parses `code` into an AST, then passes that to `transform`, which is 33 | expected to mutate the AST somehow. 34 | 35 | Once the Promise returned by `transform` has resolved, it converts the AST 36 | back into a string, and returns you a [Result](/api/ee-types.md#result-exported-type), which has 37 | the transformed string on it as its `code` property. 38 | 39 | ```ts 40 | (code: string, transform: (ast: AST) => Promise): Promise; 41 | ``` 42 | 43 | ## Transmute(...) (call signature) 44 | 45 | Parses `code` into an AST, then passes that to `transform`, which is 46 | expected to mutate the AST somehow. 47 | 48 | Once the Promise returned by `transform` has resolved, it converts the AST 49 | back into a string, and returns you a [Result](/api/ee-types.md#result-exported-type), which has 50 | the transformed string on it as its `code` property. 51 | 52 | The contents of `options` will determine what syntax options to use to 53 | parse the code, and whether to consume/generate source maps. See 54 | [Options](/api/ee-types.md#options-exported-type) for more details. 55 | 56 | ```ts 57 | (code: string, options: Options, transform: (ast: types.Node) => Promise): Promise; 58 | ``` 59 | 60 | ## Transmute(...) (call signature) 61 | 62 | Parses `code` into an AST, then passes that to `transform`, which 63 | is expected to mutate the AST somehow. 64 | 65 | Then, it converts the AST back into a string, and returns you a 66 | [Result](/api/ee-types.md#result-exported-type), which has the transformed string on it as its 67 | `code` property. 68 | 69 | ```ts 70 | (code: string, transform: (ast: AST) => void): Result; 71 | ``` 72 | 73 | ## Transmute(...) (call signature) 74 | 75 | Parses `code` into an AST, then passes that to `transform`, which is 76 | expected to mutate the AST somehow. 77 | 78 | Then, it converts the AST back into a string, and returns you a 79 | [Result](/api/ee-types.md#result-exported-type), which has the transformed string on it as its 80 | `code` property. 81 | 82 | The contents of `options` will determine what syntax options to use to 83 | parse the code, and whether to consume/generate source maps. See 84 | [Options](/api/ee-types.md#options-exported-type) for more details. 85 | 86 | ```ts 87 | (code: string, options: Options, transform: (ast: types.Node) => void): Result; 88 | ``` 89 | 90 | # parse (exported binding) 91 | 92 | Parser (code-to-AST) function used by `transmute`. See [parse](/api/parser.md#parse-exported-parse). 93 | 94 | ```ts 95 | export { parse }; 96 | ``` 97 | 98 | # Parse (exported binding) 99 | 100 | Type of the `parse` function. See [Parse](/api/parser.md#parse-exported-interface). 101 | 102 | ```ts 103 | export { Parse }; 104 | ``` 105 | 106 | # print (exported binding) 107 | 108 | Printer (AST-to-code) function used by `transmute`. See [print](/api/printer.md#print-exported-function). 109 | 110 | ```ts 111 | export { print }; 112 | ``` 113 | 114 | # traverse (exported binding) 115 | 116 | Re-export of `@babel/traverse`'s default export. 117 | 118 | ```ts 119 | export { traverse }; 120 | ``` 121 | 122 | # types (exported binding) 123 | 124 | Contains the named exports of both `@babel/types` and `@babel/traverse`. 125 | 126 | ```ts 127 | export { types }; 128 | ``` 129 | 130 | # template (exported binding) 131 | 132 | Re-export of `@babel/template`'s default export. 133 | 134 | ```ts 135 | export { template }; 136 | ``` 137 | 138 | # AST (exported type) 139 | 140 | Type returned by [parse](/api/parser.md#parse-exported-parse). See [AST](/api/ee-types.md#ast-exported-type). 141 | 142 | ```ts 143 | export { type AST }; 144 | ``` 145 | 146 | # Options (exported type) 147 | 148 | Type used by [transmute](/api/index.md#transmute-exported-function), [parse](/api/parser.md#parse-exported-parse), and [print](/api/printer.md#print-exported-function). See 149 | [Options](/api/ee-types.md#options-exported-type). 150 | 151 | ```ts 152 | export { type Options }; 153 | ``` 154 | 155 | # Result (exported type) 156 | 157 | Type returned by [print](/api/printer.md#print-exported-function). See [Result](/api/ee-types.md#result-exported-type). 158 | 159 | ```ts 160 | export { type Result }; 161 | ``` 162 | 163 | # clone (exported binding) 164 | 165 | AST node cloner utility function. See [clone](/api/utils.md#clone-exported-function). 166 | 167 | ```ts 168 | export { clone }; 169 | ``` 170 | 171 | # hasShape (exported binding) 172 | 173 | Deep object property comparison checker utility function. See 174 | [hasShape](/api/utils.md#hasshape-exported-function). 175 | 176 | ```ts 177 | export { hasShape }; 178 | ``` 179 | 180 | # mapFiles (exported binding) 181 | 182 | Bulk file content transform utility functions. See [mapFiles.fromPaths](/api/map-files.md#frompaths-exported-function). 183 | 184 | ```ts 185 | export { mapFiles }; 186 | ``` 187 | -------------------------------------------------------------------------------- /src/map-files.ts: -------------------------------------------------------------------------------- 1 | import fsp from "node:fs/promises"; 2 | import tinyglobby = require("tinyglobby"); 3 | import { runJobs } from "parallel-park"; 4 | import { Result } from "./ee-types"; 5 | import makeDebug from "debug"; 6 | 7 | const debug = makeDebug("equivalent-exchange:map-files"); 8 | const debugContent = makeDebug("equivalent-exchange:map-files:content"); 9 | 10 | export type FileStatus = 11 | | { status: "unchanged" } 12 | | { status: "changed"; before: string; after: string } 13 | | { status: "errored"; error: Error }; 14 | 15 | export type FileResults = Map; 16 | 17 | export type TransformFunction = ( 18 | fileContent: string, 19 | filePath: string, 20 | ) => TransformReturn; 21 | 22 | export type TransformReturn = 23 | | string 24 | | void 25 | | Result 26 | | Promise; 27 | 28 | /** 29 | * Helper function which applies a transform function to a set of files, 30 | * specified via glob patterns. 31 | * 32 | * Each file's content will be read as a UTF-8 string and then provided to the 33 | * transform function. If the transform function returns a *different* UTF-8 34 | * string, the file will be updated such that its content is equivalent to the 35 | * string returned from the transform function. 36 | * 37 | * Transform functions can return `undefined` to skip updating a file. 38 | * 39 | * The return value of `fromGlob` is a Map whose keys are filepath strings and 40 | * whose value is one of the following: 41 | * 42 | * - `{ status: "unchanged" }`, which indicates the file was not modified 43 | * - `{ status: "changed", before: string, after: string }`, which indicates the file WAS modified 44 | * - `{ status: "errored", error: Error }`, which indicates that the file could not be processed due to an error. 45 | */ 46 | export async function fromGlob( 47 | patterns: string | Array, 48 | transform: TransformFunction, 49 | ): Promise; 50 | export async function fromGlob( 51 | patterns: string | Array, 52 | globOptions: tinyglobby.GlobOptions | undefined, 53 | transform: TransformFunction, 54 | ): Promise; 55 | export async function fromGlob( 56 | patterns: string | Array, 57 | ...maybeOptionsAndTransform: any 58 | ): Promise { 59 | let globOptions: tinyglobby.GlobOptions | undefined; 60 | let transform: TransformFunction; 61 | if (maybeOptionsAndTransform.length === 2) { 62 | [globOptions, transform] = maybeOptionsAndTransform; 63 | } else { 64 | [transform] = maybeOptionsAndTransform; 65 | globOptions = undefined; 66 | } 67 | debug("Globbing...", { patterns, globOptions }); 68 | const paths = await tinyglobby.glob(patterns, globOptions); 69 | return fromPaths(paths, transform); 70 | } 71 | 72 | /** 73 | * Helper function which applies a transform function to a set of files, 74 | * specified via an array of filepath strings. 75 | * 76 | * Each file's content will be read as a UTF-8 string and then provided to the 77 | * transform function. If the transform function returns a *different* UTF-8 78 | * string, the file will be updated such that its content is equivalent to the 79 | * string returned from the transform function. 80 | * 81 | * Transform functions can return `undefined` to skip updating a file. 82 | * 83 | * The return value of `fromGlob` is a Map whose keys are filepath strings and 84 | * whose value is one of the following: 85 | * 86 | * - `{ status: "unchanged" }`, which indicates the file was not modified 87 | * - `{ status: "changed", before: string, after: string }`, which indicates the file WAS modified 88 | * - `{ status: "errored", error: Error }`, which indicates that the file could not be processed due to an error. 89 | */ 90 | export async function fromPaths( 91 | filePaths: Array, 92 | transform: TransformFunction, 93 | ): Promise { 94 | const results: FileResults = new Map(); 95 | debug(`Processing ${filePaths.length} files, up to 8 at a time`); 96 | await runJobs(filePaths, async (filePath) => { 97 | try { 98 | debug("Reading:", filePath); 99 | const content = await fsp.readFile(filePath, "utf-8"); 100 | debugContent(filePath, "content before:\n", content); 101 | debug("Transforming:", filePath); 102 | const result = await transform(content, filePath); 103 | debug("Done Transforming:", filePath); 104 | if (result === undefined || result === content) { 105 | debug("Unchanged:", filePath); 106 | results.set(filePath, { status: "unchanged" }); 107 | } else { 108 | let newContent: string; 109 | if (typeof result === "string") { 110 | newContent = result; 111 | } else if ( 112 | typeof result === "object" && 113 | result != null && 114 | "code" in result && 115 | typeof result.code === "string" 116 | ) { 117 | newContent = result.code; 118 | } else { 119 | debug("Transform returned invalid value:", { filePath, result }); 120 | throw new Error( 121 | `Transform returned invalid value: ${String(result)}`, 122 | ); 123 | } 124 | 125 | debugContent(filePath, "content after:\n", newContent); 126 | debug("Writing:", filePath); 127 | await fsp.writeFile(filePath, newContent); 128 | results.set(filePath, { 129 | status: "changed", 130 | before: content, 131 | after: newContent, 132 | }); 133 | } 134 | } catch (err: any) { 135 | debug("Errored:", { filePath, err }); 136 | results.set(filePath, { 137 | status: "errored", 138 | error: err, 139 | }); 140 | } 141 | }); 142 | debug(`Done processing ${filePaths.length} files.`); 143 | return results; 144 | } 145 | -------------------------------------------------------------------------------- /api/ee-types.md: -------------------------------------------------------------------------------- 1 | # AST (exported type) 2 | 3 | The node type that `transmute`/`parse` will give to you as the root AST node. 4 | 5 | It's an alias to babel's `File` node type. 6 | 7 | ```ts 8 | type AST = types.File; 9 | ``` 10 | 11 | # Options (exported type) 12 | 13 | Options type used by `transmute`, `parse`, and `print`. 14 | 15 | ```ts 16 | type Options = { 17 | fileName?: string; 18 | sourceMapFileName?: string; 19 | inputSourceMap?: any; 20 | parseOptions?: { 21 | typeSyntax?: "typescript" | "typescript-dts" | "flow"; 22 | decoratorSyntax?: "new" | "legacy"; 23 | pipelineSyntax?: "minimal" | "fsharp" | "hack" | "smart" | "none"; 24 | hackPipelineTopicToken?: "^^" | "@@" | "^" | "%" | "#"; 25 | jsxEnabled?: boolean; 26 | v8Intrinsic?: boolean; 27 | placeholders?: boolean; 28 | expression?: boolean; 29 | skipRecast?: boolean; 30 | }; 31 | printOptions?: { 32 | printMethod?: "recast.print" | "recast.prettyPrint" | "@babel/generator"; 33 | }; 34 | }; 35 | ``` 36 | 37 | ## Options.fileName (string property) 38 | 39 | The name of the file whose code you are transmuting. 40 | 41 | If passed, parse errors thrown by `transmute` will be clearer, and it will 42 | be possible to generate sourcemaps for the file. 43 | 44 | ```ts 45 | fileName?: string; 46 | ``` 47 | 48 | ## Options.sourceMapFileName (string property) 49 | 50 | The name of the output file where you will store a sourcemap generated by 51 | transmute. 52 | 53 | Both `fileName` and `sourceMapFileName` must be present to generate a 54 | sourcemap. 55 | 56 | ```ts 57 | sourceMapFileName?: string; 58 | ``` 59 | 60 | ## Options.inputSourceMap (any property) 61 | 62 | An input sourcemap to compose the newly-generated sourcemap with. 63 | 64 | ```ts 65 | inputSourceMap?: any; 66 | ``` 67 | 68 | ## Options.parseOptions (object property) 69 | 70 | Options that control how code strings will be converted into ASTs. 71 | 72 | ```ts 73 | parseOptions?: { 74 | typeSyntax?: "typescript" | "typescript-dts" | "flow"; 75 | decoratorSyntax?: "new" | "legacy"; 76 | pipelineSyntax?: "minimal" | "fsharp" | "hack" | "smart" | "none"; 77 | hackPipelineTopicToken?: "^^" | "@@" | "^" | "%" | "#"; 78 | jsxEnabled?: boolean; 79 | v8Intrinsic?: boolean; 80 | placeholders?: boolean; 81 | expression?: boolean; 82 | skipRecast?: boolean; 83 | }; 84 | ``` 85 | 86 | ### Options.parseOptions.typeSyntax (property) 87 | 88 | Which type-checker syntax to use. 89 | 90 | Defaults to "typescript". 91 | 92 | ```ts 93 | typeSyntax?: "typescript" | "typescript-dts" | "flow"; 94 | ``` 95 | 96 | ### Options.parseOptions.decoratorSyntax (property) 97 | 98 | Which decorator proposal syntax to use. 99 | 100 | Defaults to "legacy". 101 | 102 | ```ts 103 | decoratorSyntax?: "new" | "legacy"; 104 | ``` 105 | 106 | ### Options.parseOptions.pipelineSyntax (property) 107 | 108 | Which syntax proposal to use for the pipeline operator. 109 | 110 | Defaults to "hack". 111 | 112 | ```ts 113 | pipelineSyntax?: "minimal" | "fsharp" | "hack" | "smart" | "none"; 114 | ``` 115 | 116 | ### Options.parseOptions.hackPipelineTopicToken (property) 117 | 118 | Which topic token to use when using the "hack" syntax proposal for the 119 | pipeline operator. 120 | 121 | Defaults to "%". Only used when pipelineSyntax is "hack". 122 | 123 | ```ts 124 | hackPipelineTopicToken?: "^^" | "@@" | "^" | "%" | "#"; 125 | ``` 126 | 127 | ### Options.parseOptions.jsxEnabled (boolean property) 128 | 129 | Whether to parse matching < and > in the code as JSX. It is generally 130 | okay to have this on, but if your code is not JSX, and you do some nested 131 | numeric comparisons, or use < and > for type casting, you should disable 132 | this. 133 | 134 | Defaults to `true`. 135 | 136 | ```ts 137 | jsxEnabled?: boolean; 138 | ``` 139 | 140 | ### Options.parseOptions.v8Intrinsic (boolean property) 141 | 142 | Whether to enable `@babel/parser`'s `v8intrinsic` plugin, which allows 143 | parsing V8 identifier syntax like `%GetOptimizationStatus(fn)`. 144 | 145 | NOTE: If you enable this, you CANNOT set pipelineSyntax to "hack" (which 146 | is the default). 147 | 148 | ```ts 149 | v8Intrinsic?: boolean; 150 | ``` 151 | 152 | ### Options.parseOptions.placeholders (boolean property) 153 | 154 | Whether to enable `@babel/parser`'s `placeholders` plugin, which allows 155 | parsing syntax like `const thing = %%SOMETHING%%`. 156 | 157 | NOTE: If you enable this, you CANNOT set pipelineSyntax to "hack" (which 158 | is the default). 159 | 160 | ```ts 161 | placeholders?: boolean; 162 | ``` 163 | 164 | ### Options.parseOptions.expression (boolean property) 165 | 166 | Whether to parse the code as an expression instead of a whole program. 167 | 168 | When this is true, the type of the resulting AST node will vary. 169 | 170 | Defaults to false. 171 | 172 | ```ts 173 | expression?: boolean; 174 | ``` 175 | 176 | ### Options.parseOptions.skipRecast (boolean property) 177 | 178 | Whether to wrap the resulting Babel AST with Recast, to keep track of 179 | changes so that code can be re-printed with minimal changes. 180 | 181 | Setting this to true reduces cpu clocktime and gc allocations, at the 182 | expense of not preserving source code formatting for non-modified parts 183 | of the AST. 184 | 185 | It might make sense to set this to true if you're going to change 186 | `printOptions.printMethod` from the default value. 187 | 188 | Defaults to false. 189 | 190 | ```ts 191 | skipRecast?: boolean; 192 | ``` 193 | 194 | ## Options.printOptions (object property) 195 | 196 | Options that control how ASTs will be converted into code strings. 197 | 198 | ```ts 199 | printOptions?: { 200 | printMethod?: "recast.print" | "recast.prettyPrint" | "@babel/generator"; 201 | }; 202 | ``` 203 | 204 | ### Options.printOptions.printMethod (property) 205 | 206 | Which method to use to convert an AST back into code. 207 | 208 | Defaults to "recast.print", which attempts to preserve source formatting 209 | of unchanged nodes. If this doesn't matter to you, you can instead use 210 | "recast.prettyPrint", which reprints all nodes with generic formatting. 211 | 212 | "@babel/generator" will use the npm package "@babel/generator" to make 213 | the code instead. If you encounter bugs with recast's printer, babel 214 | generator may work better. 215 | 216 | ```ts 217 | printMethod?: "recast.print" | "recast.prettyPrint" | "@babel/generator"; 218 | ``` 219 | 220 | # Result (exported type) 221 | 222 | The result of transmuting some code. If the options you passed into 223 | `transmute` had sourcemap-related stuff set, then the `map` property 224 | will be set. Otherwise, it'll be null. 225 | 226 | ```ts 227 | type Result = { 228 | code: string; 229 | map?: any; 230 | }; 231 | ``` 232 | 233 | ## Result.code (string property) 234 | 235 | The transformed code string. 236 | 237 | ```ts 238 | code: string; 239 | ``` 240 | 241 | ## Result.map (any property) 242 | 243 | A source map describing the changes between the original code string and 244 | its transformed version. 245 | 246 | ```ts 247 | map?: any; 248 | ``` 249 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | import * as babelParser from "@babel/parser"; 2 | import * as recast from "recast"; 3 | import * as types from "./types-ns"; 4 | import { AST, Options } from "./ee-types"; 5 | import makeDebug from "debug"; 6 | 7 | const debug = makeDebug("equivalent-exchange:parser"); 8 | 9 | /** 10 | * The various call signatures of the {@link parse} function. When option 11 | * `parseOptions.expression` is true, it returns a `types.Node`, but when it 12 | * isn't, it returns an `AST`, which is an alias for `types.File`. 13 | */ 14 | export interface Parse { 15 | (code: string): AST; 16 | ( 17 | code: string, 18 | options: Options & { parseOptions: { expression: true } }, 19 | ): types.Node; 20 | ( 21 | code: string, 22 | options: Options & { parseOptions: { expression: false } }, 23 | ): AST; 24 | ( 25 | code: string, 26 | options: Options & { parseOptions: { expression: boolean } }, 27 | ): types.Node; 28 | (code: string, options: Options): AST; 29 | } 30 | 31 | /** 32 | * Parses a JavaScript/TypeScript code string into an AST. 33 | * 34 | * This function is used internally by {@link transmute}. 35 | * 36 | * The options parameter is the same type as the options parameter for `transmute`. 37 | */ 38 | export const parse: Parse = (source: string, options?: Options): any => { 39 | debug("parse", { source, options }); 40 | if (options) { 41 | const maybeWrongOpts = options as any; 42 | for (const parseOptionsKey of [ 43 | "typeSyntax", 44 | "decoratorSyntax", 45 | "pipelineSyntax", 46 | "hackPipelineTopicToken", 47 | "jsxEnabled", 48 | "v8Intrinsic", 49 | "placeholders", 50 | "expression", 51 | "skipRecast", 52 | ]) { 53 | if (parseOptionsKey in maybeWrongOpts) { 54 | throw new Error( 55 | "`parse` function received a legacy ParseOptions, but we want an Options. The following property should be in a sub-object under `parseOptions`: " + 56 | parseOptionsKey, 57 | ); 58 | } 59 | } 60 | } 61 | 62 | const typeSyntax = options?.parseOptions?.typeSyntax || "typescript"; 63 | const decoratorSyntax = options?.parseOptions?.decoratorSyntax || "legacy"; 64 | const pipelineSyntax = options?.parseOptions?.pipelineSyntax || "hack"; 65 | const hackPipelineTopicToken = 66 | options?.parseOptions?.hackPipelineTopicToken || "%"; 67 | const jsxEnabled = options?.parseOptions?.jsxEnabled !== false; 68 | const v8Intrinsic = options?.parseOptions?.v8Intrinsic ?? false; 69 | const placeholders = options?.parseOptions?.placeholders ?? false; 70 | 71 | const plugins: babelParser.ParserOptions["plugins"] = [ 72 | "asyncDoExpressions", 73 | "asyncGenerators", 74 | "bigInt", 75 | "classPrivateMethods", 76 | "classPrivateProperties", 77 | "classProperties", 78 | "classStaticBlock", 79 | "decimal", 80 | 81 | "doExpressions", 82 | "dynamicImport", 83 | "exportDefaultFrom", 84 | "exportNamespaceFrom", 85 | "functionBind", 86 | "functionSent", 87 | "importAssertions", 88 | "importMeta", 89 | "logicalAssignment", 90 | "moduleBlocks", 91 | "moduleStringNames", 92 | "nullishCoalescingOperator", 93 | "numericSeparator", 94 | "objectRestSpread", 95 | "optionalCatchBinding", 96 | "optionalChaining", 97 | "partialApplication", 98 | "privateIn", 99 | "throwExpressions", 100 | "topLevelAwait", 101 | ]; 102 | 103 | if (typeSyntax === "flow") { 104 | plugins.push("flow", "flowComments"); 105 | } else if (typeSyntax === "typescript") { 106 | plugins.push("typescript"); 107 | } else if (typeSyntax === "typescript-dts") { 108 | plugins.push(["typescript", { dts: true }]); 109 | } 110 | 111 | if (jsxEnabled) { 112 | plugins.push("jsx"); 113 | } 114 | 115 | if (decoratorSyntax === "new") { 116 | plugins.push("decorators"); 117 | } else { 118 | plugins.push("decorators-legacy"); 119 | } 120 | 121 | if (v8Intrinsic && placeholders) { 122 | throw new Error( 123 | "Babel disallows using both v8Intrinsic and placeholders together at the same time. Either disable the 'v8Intrinsic' option or disable the 'placeholders' option.", 124 | ); 125 | } 126 | 127 | if (v8Intrinsic) { 128 | if (pipelineSyntax === "hack") { 129 | throw new Error( 130 | "Babel disallows using both v8Intrinsic and Hack-style pipes together. `equivalent-exchange` has hack-style pipeline syntax enabled by default. Either disable the 'v8Intrinsic' option or change the 'pipelineSyntax' option to a different value, such as 'none' (it defaults to 'hack').", 131 | ); 132 | } 133 | plugins.push("v8intrinsic"); 134 | } 135 | 136 | if (placeholders) { 137 | if (pipelineSyntax === "hack") { 138 | throw new Error( 139 | "Babel disallows using both placeholders and Hack-style pipes together. `equivalent-exchange` has hack-style pipeline syntax enabled by default. Either disable the 'placeholders' option or change the 'pipelineSyntax' option to a different value, such as 'none' (it defaults to 'hack').", 140 | ); 141 | } 142 | plugins.push("placeholders"); 143 | } 144 | 145 | if (pipelineSyntax !== "none") { 146 | plugins.push([ 147 | "pipelineOperator", 148 | { 149 | proposal: pipelineSyntax, 150 | ...(pipelineSyntax === "hack" 151 | ? { 152 | // Babel's type disallows "^", but babel's runtime 153 | // seems to allow it, so ignore this error. 154 | topicToken: hackPipelineTopicToken as any, 155 | } 156 | : {}), 157 | }, 158 | ]); 159 | } 160 | 161 | debug("babel plugins", plugins); 162 | 163 | const codeToParse = options?.parseOptions?.expression 164 | ? `(${source})` 165 | : source; 166 | 167 | function doBabelParse(codeForBabel: string) { 168 | const result = babelParser.parse(codeForBabel, { 169 | sourceFilename: options?.fileName, 170 | allowAwaitOutsideFunction: true, 171 | allowImportExportEverywhere: true, 172 | allowReturnOutsideFunction: true, 173 | allowSuperOutsideMethod: true, 174 | allowUndeclaredExports: true, 175 | tokens: true, 176 | plugins, 177 | }); 178 | return result; 179 | } 180 | 181 | let ast: babelParser.ParseResult; 182 | if (options?.parseOptions?.skipRecast) { 183 | debug("skipping recast; parsing with babel only"); 184 | ast = doBabelParse(source); 185 | } else { 186 | debug("parsing with babel + recast"); 187 | ast = recast.parse(codeToParse, { 188 | sourceFileName: options?.fileName, 189 | inputSourceMap: options?.inputSourceMap, 190 | parser: { 191 | parse(sourceFromRecast: string) { 192 | return doBabelParse(sourceFromRecast); 193 | }, 194 | }, 195 | }); 196 | } 197 | 198 | debug("done parsing"); 199 | if (options?.parseOptions?.expression) { 200 | if (!types.isExpressionStatement(ast.program.body[0])) { 201 | throw new Error( 202 | "Attempted to parse code as an expression, but the resulting AST's first statement wasn't an ExpressionStatement.", 203 | ); 204 | } 205 | 206 | return ast.program.body[0].expression; 207 | } 208 | 209 | return ast; 210 | }; 211 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import util from "node:util"; 2 | import { describe, test, expect } from "vitest"; 3 | import { 4 | AST, 5 | transmute, 6 | traverse, 7 | types, 8 | clone, 9 | hasShape, 10 | parse, 11 | print, 12 | } from "./index"; 13 | 14 | test("basic usage", async () => { 15 | const code = `console.log("hello!");`; 16 | 17 | const transform = (ast: AST) => { 18 | traverse(ast, { 19 | StringLiteral(path) { 20 | const { node } = path; 21 | if (node.value === "hello!") { 22 | path.replaceWith(types.stringLiteral("goodbye!")); 23 | } 24 | }, 25 | }); 26 | }; 27 | 28 | const result1 = transmute(code, transform); 29 | const result2 = await transmute(code, async (ast) => transform(ast)); 30 | 31 | expect(result1).toEqual(result2); 32 | expect(result1).toMatchInlineSnapshot(` 33 | { 34 | "code": "console.log("goodbye!");", 35 | "map": null, 36 | } 37 | `); 38 | }); 39 | 40 | test("with source map", async () => { 41 | const code = `console.log("hello!");`; 42 | 43 | const transform = (ast: types.Node) => { 44 | traverse(ast, { 45 | StringLiteral(path) { 46 | const { node } = path; 47 | if (node.value === "hello!") { 48 | path.replaceWith(types.stringLiteral("goodbye!")); 49 | } 50 | }, 51 | }); 52 | }; 53 | 54 | const options = { 55 | fileName: "src/index.js", 56 | sourceMapFileName: "src/index.js.map", 57 | }; 58 | const result1 = transmute(code, options, transform); 59 | const result2 = await transmute(code, options, async (ast) => transform(ast)); 60 | 61 | expect(result1).toEqual(result2); 62 | expect(result1).toMatchInlineSnapshot(` 63 | { 64 | "code": "console.log("goodbye!");", 65 | "map": { 66 | "file": "src/index.js.map", 67 | "mappings": "AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAS,CAAC", 68 | "names": [], 69 | "sources": [ 70 | "src/index.js", 71 | ], 72 | "sourcesContent": [ 73 | "console.log("hello!");", 74 | ], 75 | "version": 3, 76 | }, 77 | } 78 | `); 79 | }); 80 | 81 | test("received AST type is File", async () => { 82 | expect.assertions(2); 83 | 84 | const code = `console.log("hello!");`; 85 | 86 | const transform = (ast: AST) => { 87 | expect(ast.type).toBe("File"); 88 | }; 89 | 90 | transmute(code, transform); 91 | await transmute(code, async (ast) => { 92 | transform(ast); 93 | }); 94 | }); 95 | 96 | test("received AST uses babel *Literal types instead of ESTree Literal type", async () => { 97 | expect.assertions(6); 98 | 99 | const code = `console.log("hello!", 2, /blah/);`; 100 | 101 | const transform = (ast: AST) => { 102 | traverse(ast, { 103 | StringLiteral(path) { 104 | expect(path.node.type).toBe("StringLiteral"); 105 | }, 106 | NumericLiteral(path) { 107 | expect(path.node.type).toBe("NumericLiteral"); 108 | }, 109 | RegExpLiteral(path) { 110 | expect(path.node.type).toBe("RegExpLiteral"); 111 | }, 112 | }); 113 | }; 114 | 115 | transmute(code, transform); 116 | await transmute(code, async (ast) => { 117 | transform(ast); 118 | }); 119 | }); 120 | 121 | test("with jsx syntax", async () => { 122 | const code = ` 123 | interface Props { 124 | greet: string; 125 | } 126 | 127 | const Component: React.FC = ({greet = "hello!"}) => { 128 | return
{greet}
129 | }`; 130 | 131 | const transform = (ast: AST) => { 132 | traverse(ast, { 133 | StringLiteral(path) { 134 | const { node } = path; 135 | if (node.value === "hello!") { 136 | path.replaceWith(types.stringLiteral("goodbye!")); 137 | } 138 | }, 139 | }); 140 | }; 141 | 142 | expect(transmute(code, transform)).toMatchInlineSnapshot(` 143 | { 144 | "code": " 145 | interface Props { 146 | greet: string; 147 | } 148 | 149 | const Component: React.FC = ({greet = "goodbye!"}) => { 150 | return
{greet}
151 | }", 152 | "map": null, 153 | } 154 | `); 155 | }); 156 | 157 | test("clone helper", async () => { 158 | const ast = parse("console.log( 'hi there!' );"); 159 | const otherAst = clone(ast); 160 | 161 | expect(otherAst).not.toBe(ast); 162 | expect(otherAst).toBeInstanceOf(ast.constructor); 163 | expect(types.isFile(ast)).toBe(true); 164 | expect(types.isFile(otherAst)).toBe(true); 165 | expect(ast.program).not.toBe(otherAst.program); 166 | expect(ast.program.body[0]).not.toBe(otherAst.program.body[0]); 167 | 168 | expect(print(ast).code).toMatchInlineSnapshot( 169 | "\"console.log( 'hi there!' );\"", 170 | ); 171 | 172 | // recast-tracked formatting is lost. I think I don't care? 173 | expect(print(otherAst).code).toMatchInlineSnapshot( 174 | '"console.log("hi there!");"', 175 | ); 176 | }); 177 | 178 | test("duplicate child", async () => { 179 | const ast = parse("console.log( hi ) ;"); 180 | 181 | ast.program.body.push(ast.program.body[0]); 182 | 183 | expect(print(ast).code).toMatchInlineSnapshot( 184 | '"console.log( hi ) ;console.log( hi ) ;"', 185 | ); 186 | }); 187 | 188 | test("cloned duplicate child", async () => { 189 | const ast = parse("console.log( hi ) ;"); 190 | 191 | ast.program.body.push(clone(ast.program.body[0])); 192 | 193 | expect(print(ast).code).toMatchInlineSnapshot( 194 | '"console.log( hi ) ;console.log(hi);"', 195 | ); 196 | }); 197 | 198 | // is this a recast bug? I don't like this, but I don't know where 199 | // the right place to fix it is. Documenting the behavior here for now. 200 | test("dangerous duplicate child", async () => { 201 | const ast = parse(" [ 1] "); 202 | 203 | ast.program.body.push(ast.program.body[0]); 204 | 205 | expect(print(ast).code).toMatchInlineSnapshot('" [ 1][ 1] "'); 206 | }); 207 | 208 | // is this a recast bug? I don't like this, but I don't know where 209 | // the right place to fix it is. Documenting the behavior here for now. 210 | test("dangerous cloned duplicate child", async () => { 211 | const ast = parse(" [ 1] "); 212 | 213 | ast.program.body.push(clone(ast.program.body[0])); 214 | 215 | expect(print(ast).code).toMatchInlineSnapshot('" [ 1][1]; "'); 216 | }); 217 | 218 | test("clone with circular structure", async () => { 219 | const a: any = { 220 | b: { something: "eggplant", 42: 42, Infinity }, 221 | c: { potato: true }, 222 | }; 223 | a.b.a = a; 224 | 225 | const a2 = clone(a); 226 | 227 | expect(a).toEqual(a2); 228 | expect(a2).toMatchInlineSnapshot(` 229 | { 230 | "b": { 231 | "42": 42, 232 | "Infinity": Infinity, 233 | "a": [Circular], 234 | "something": "eggplant", 235 | }, 236 | "c": { 237 | "potato": true, 238 | }, 239 | } 240 | `); 241 | }); 242 | 243 | test("clone large ast structure", async () => { 244 | console.log("before parse"); 245 | const a = parse( 246 | `declare type TypeValidator = (value: any) => value is T; 247 | 248 | declare type CoerceToTypeValidator = 249 | V extends StringConstructor 250 | ? TypeValidator 251 | : V extends NumberConstructor 252 | ? TypeValidator 253 | : V extends BooleanConstructor 254 | ? TypeValidator 255 | : V extends BigIntConstructor 256 | ? TypeValidator 257 | : V extends SymbolConstructor 258 | ? TypeValidator 259 | : V extends RegExpConstructor 260 | ? TypeValidator 261 | : V extends ArrayConstructor 262 | ? TypeValidator> 263 | : V extends SetConstructor 264 | ? TypeValidator> 265 | : V extends MapConstructor 266 | ? TypeValidator> 267 | : V extends ObjectConstructor 268 | ? TypeValidator<{ 269 | [key: string | number | symbol]: unknown; 270 | }> 271 | : V extends DateConstructor 272 | ? TypeValidator 273 | : V extends FunctionConstructor 274 | ? TypeValidator 275 | : V extends ArrayBufferConstructor 276 | ? TypeValidator 277 | : V extends SharedArrayBufferConstructor 278 | ? TypeValidator 279 | : V extends DataViewConstructor 280 | ? TypeValidator 281 | : V extends Int8ArrayConstructor 282 | ? TypeValidator 283 | : V extends Uint8ArrayConstructor 284 | ? TypeValidator 285 | : V extends Uint8ClampedArrayConstructor 286 | ? TypeValidator 287 | : V extends Int16ArrayConstructor 288 | ? TypeValidator 289 | : V extends Uint16ArrayConstructor 290 | ? TypeValidator 291 | : V extends Int32ArrayConstructor 292 | ? TypeValidator 293 | : V extends Uint32ArrayConstructor 294 | ? TypeValidator 295 | : V extends Float32ArrayConstructor 296 | ? TypeValidator 297 | : V extends Float64ArrayConstructor 298 | ? TypeValidator 299 | : V extends RegExp 300 | ? TypeValidator 301 | : V extends {} 302 | ? TypeValidator<{ 303 | [key in keyof V]: CoerceToTypeValidator; 304 | }> 305 | : V extends [] 306 | ? TypeValidator<[]> 307 | : V extends [any] 308 | ? TypeValidator>> 309 | : V extends Array 310 | ? TypeValidator> 311 | : V extends { 312 | new (...args: any): any; 313 | } 314 | ? TypeValidator> 315 | : TypeValidator; 316 | 317 | declare type CoerceableToTypeValidator = 318 | | boolean 319 | | number 320 | | string 321 | | bigint 322 | | undefined 323 | | null 324 | | RegExp 325 | | StringConstructor 326 | | NumberConstructor 327 | | BooleanConstructor 328 | | BigIntConstructor 329 | | SymbolConstructor 330 | | RegExpConstructor 331 | | ArrayConstructor 332 | | SetConstructor 333 | | MapConstructor 334 | | ObjectConstructor 335 | | DateConstructor 336 | | FunctionConstructor 337 | | ArrayBufferConstructor 338 | | SharedArrayBufferConstructor 339 | | DataViewConstructor 340 | | Int8ArrayConstructor 341 | | Uint8ArrayConstructor 342 | | Uint8ClampedArrayConstructor 343 | | Int16ArrayConstructor 344 | | Uint16ArrayConstructor 345 | | Int32ArrayConstructor 346 | | Uint32ArrayConstructor 347 | | Float32ArrayConstructor 348 | | Float64ArrayConstructor 349 | | {} 350 | | [] 351 | | [any] 352 | | Array 353 | | { 354 | new (...args: any): any; 355 | }; 356 | 357 | declare type UnwrapTypeFromCoerceableOrValidator< 358 | V extends CoerceableToTypeValidator | TypeValidator | unknown 359 | > = V extends TypeValidator 360 | ? T 361 | : V extends CoerceableToTypeValidator 362 | ? CoerceToTypeValidator extends TypeValidator 363 | ? T 364 | : never 365 | : unknown; 366 | 367 | declare const types: { 368 | // basic types 369 | any: TypeValidator; 370 | unknown: TypeValidator; 371 | anyObject: TypeValidator<{ 372 | [key: string | number | symbol]: any; 373 | }>; 374 | unknownObject: TypeValidator<{}>; 375 | object: TypeValidator<{}>; 376 | Object: TypeValidator<{}>; 377 | arrayOfAny: TypeValidator>; 378 | arrayOfUnknown: TypeValidator>; 379 | array: TypeValidator>; 380 | Array: TypeValidator; 381 | anyArray: TypeValidator>; 382 | boolean: TypeValidator; 383 | Boolean: TypeValidator; 384 | string: TypeValidator; 385 | String: TypeValidator; 386 | null: TypeValidator; 387 | undefined: TypeValidator; 388 | nullish: TypeValidator; 389 | void: TypeValidator; 390 | numberIncludingNanAndInfinities: TypeValidator; 391 | number: TypeValidator; 392 | Number: TypeValidator; 393 | NaN: TypeValidator; 394 | Infinity: TypeValidator; 395 | NegativeInfinity: TypeValidator; 396 | integer: TypeValidator; 397 | bigint: TypeValidator; 398 | BigInt: TypeValidator; 399 | never: TypeValidator; 400 | anyFunction: TypeValidator<(...args: any) => any>; 401 | unknownFunction: TypeValidator<(...args: Array) => unknown>; 402 | Function: TypeValidator<(...args: Array) => unknown>; 403 | false: TypeValidator; 404 | true: TypeValidator; 405 | falsy: TypeValidator; 406 | truthy: (target: false | "" | 0 | T | null | undefined) => target is T; 407 | nonNullOrUndefined: (target: T | null | undefined) => target is T; 408 | Error: TypeValidator; 409 | Symbol: TypeValidator; 410 | symbol: TypeValidator; 411 | RegExp: TypeValidator; 412 | Date: TypeValidator; 413 | anyMap: TypeValidator>; 414 | unknownMap: TypeValidator>; 415 | map: TypeValidator>; 416 | Map: TypeValidator>; 417 | anySet: TypeValidator>; 418 | unknownSet: TypeValidator>; 419 | set: TypeValidator>; 420 | Set: TypeValidator>; 421 | ArrayBuffer: TypeValidator; 422 | SharedArrayBuffer: TypeValidator; 423 | DataView: TypeValidator; 424 | TypedArray: TypeValidator< 425 | | Int8Array 426 | | Uint8Array 427 | | Uint8ClampedArray 428 | | Int16Array 429 | | Uint16Array 430 | | Int32Array 431 | | Uint32Array 432 | | Float32Array 433 | | Float64Array 434 | >; 435 | Int8Array: TypeValidator; 436 | Uint8Array: TypeValidator; 437 | Uint8ClampedArray: TypeValidator; 438 | Int16Array: TypeValidator; 439 | Uint16Array: TypeValidator; 440 | Int32Array: TypeValidator; 441 | Uint32Array: TypeValidator; 442 | Float32Array: TypeValidator; 443 | Float64Array: TypeValidator; 444 | 445 | // type constructors 446 | exactString(str: T): TypeValidator; 447 | exactNumber(num: T): TypeValidator; 448 | exactBigInt(num: T): TypeValidator; 449 | exactSymbol(sym: T): TypeValidator; 450 | hasClassName( 451 | name: Name 452 | ): TypeValidator<{ constructor: Function & { name: Name } }>; 453 | hasToStringTag(name: string): TypeValidator; 454 | instanceOf( 455 | klass: Klass 456 | ): TypeValidator; 457 | stringMatching(regexp: RegExp): TypeValidator; 458 | symbolFor(key: string): TypeValidator; 459 | arrayOf | CoerceableToTypeValidator | unknown>( 460 | typeValidator: T 461 | ): TypeValidator>>; 462 | intersection: { 463 | < 464 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 465 | Second extends TypeValidator | CoerceableToTypeValidator | unknown 466 | >( 467 | first: First, 468 | second: Second 469 | ): TypeValidator< 470 | UnwrapTypeFromCoerceableOrValidator & 471 | UnwrapTypeFromCoerceableOrValidator 472 | >; 473 | < 474 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 475 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 476 | Third extends TypeValidator | CoerceableToTypeValidator | unknown 477 | >( 478 | first: First, 479 | second: Second, 480 | third: Third 481 | ): TypeValidator< 482 | UnwrapTypeFromCoerceableOrValidator & 483 | UnwrapTypeFromCoerceableOrValidator & 484 | UnwrapTypeFromCoerceableOrValidator 485 | >; 486 | < 487 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 488 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 489 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 490 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown 491 | >( 492 | first: First, 493 | second: Second, 494 | third: Third, 495 | fourth: Fourth 496 | ): TypeValidator< 497 | UnwrapTypeFromCoerceableOrValidator & 498 | UnwrapTypeFromCoerceableOrValidator & 499 | UnwrapTypeFromCoerceableOrValidator & 500 | UnwrapTypeFromCoerceableOrValidator 501 | >; 502 | < 503 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 504 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 505 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 506 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 507 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown 508 | >( 509 | first: First, 510 | second: Second, 511 | third: Third, 512 | fourth: Fourth, 513 | fifth: Fifth 514 | ): TypeValidator< 515 | UnwrapTypeFromCoerceableOrValidator & 516 | UnwrapTypeFromCoerceableOrValidator & 517 | UnwrapTypeFromCoerceableOrValidator & 518 | UnwrapTypeFromCoerceableOrValidator & 519 | UnwrapTypeFromCoerceableOrValidator 520 | >; 521 | < 522 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 523 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 524 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 525 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 526 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 527 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown 528 | >( 529 | first: First, 530 | second: Second, 531 | third: Third, 532 | fourth: Fourth, 533 | fifth: Fifth, 534 | sixth: Sixth 535 | ): TypeValidator< 536 | UnwrapTypeFromCoerceableOrValidator & 537 | UnwrapTypeFromCoerceableOrValidator & 538 | UnwrapTypeFromCoerceableOrValidator & 539 | UnwrapTypeFromCoerceableOrValidator & 540 | UnwrapTypeFromCoerceableOrValidator & 541 | UnwrapTypeFromCoerceableOrValidator 542 | >; 543 | < 544 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 545 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 546 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 547 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 548 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 549 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 550 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown 551 | >( 552 | first: First, 553 | second: Second, 554 | third: Third, 555 | fourth: Fourth, 556 | fifth: Fifth, 557 | sixth: Sixth, 558 | seventh: Seventh 559 | ): TypeValidator< 560 | UnwrapTypeFromCoerceableOrValidator & 561 | UnwrapTypeFromCoerceableOrValidator & 562 | UnwrapTypeFromCoerceableOrValidator & 563 | UnwrapTypeFromCoerceableOrValidator & 564 | UnwrapTypeFromCoerceableOrValidator & 565 | UnwrapTypeFromCoerceableOrValidator & 566 | UnwrapTypeFromCoerceableOrValidator 567 | >; 568 | < 569 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 570 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 571 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 572 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 573 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 574 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 575 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 576 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown 577 | >( 578 | first: First, 579 | second: Second, 580 | third: Third, 581 | fourth: Fourth, 582 | fifth: Fifth, 583 | sixth: Sixth, 584 | seventh: Seventh, 585 | eighth: Eighth 586 | ): TypeValidator< 587 | UnwrapTypeFromCoerceableOrValidator & 588 | UnwrapTypeFromCoerceableOrValidator & 589 | UnwrapTypeFromCoerceableOrValidator & 590 | UnwrapTypeFromCoerceableOrValidator & 591 | UnwrapTypeFromCoerceableOrValidator & 592 | UnwrapTypeFromCoerceableOrValidator & 593 | UnwrapTypeFromCoerceableOrValidator & 594 | UnwrapTypeFromCoerceableOrValidator 595 | >; 596 | < 597 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 598 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 599 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 600 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 601 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 602 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 603 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 604 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown, 605 | Ninth extends TypeValidator | CoerceableToTypeValidator | unknown 606 | >( 607 | first: First, 608 | second: Second, 609 | third: Third, 610 | fourth: Fourth, 611 | fifth: Fifth, 612 | sixth: Sixth, 613 | seventh: Seventh, 614 | eighth: Eighth, 615 | ninth: Ninth 616 | ): TypeValidator< 617 | UnwrapTypeFromCoerceableOrValidator & 618 | UnwrapTypeFromCoerceableOrValidator & 619 | UnwrapTypeFromCoerceableOrValidator & 620 | UnwrapTypeFromCoerceableOrValidator & 621 | UnwrapTypeFromCoerceableOrValidator & 622 | UnwrapTypeFromCoerceableOrValidator & 623 | UnwrapTypeFromCoerceableOrValidator & 624 | UnwrapTypeFromCoerceableOrValidator & 625 | UnwrapTypeFromCoerceableOrValidator 626 | >; 627 | < 628 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 629 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 630 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 631 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 632 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 633 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 634 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 635 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown, 636 | Ninth extends TypeValidator | CoerceableToTypeValidator | unknown, 637 | Tenth extends TypeValidator | CoerceableToTypeValidator | unknown 638 | >( 639 | first: First, 640 | second: Second, 641 | third: Third, 642 | fourth: Fourth, 643 | fifth: Fifth, 644 | sixth: Sixth, 645 | seventh: Seventh, 646 | eighth: Eighth, 647 | ninth: Ninth, 648 | tenth: Tenth 649 | ): TypeValidator< 650 | UnwrapTypeFromCoerceableOrValidator & 651 | UnwrapTypeFromCoerceableOrValidator & 652 | UnwrapTypeFromCoerceableOrValidator & 653 | UnwrapTypeFromCoerceableOrValidator & 654 | UnwrapTypeFromCoerceableOrValidator & 655 | UnwrapTypeFromCoerceableOrValidator & 656 | UnwrapTypeFromCoerceableOrValidator & 657 | UnwrapTypeFromCoerceableOrValidator & 658 | UnwrapTypeFromCoerceableOrValidator & 659 | UnwrapTypeFromCoerceableOrValidator 660 | >; 661 | }; 662 | and: { 663 | < 664 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 665 | Second extends TypeValidator | CoerceableToTypeValidator | unknown 666 | >( 667 | first: First, 668 | second: Second 669 | ): TypeValidator< 670 | UnwrapTypeFromCoerceableOrValidator & 671 | UnwrapTypeFromCoerceableOrValidator 672 | >; 673 | < 674 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 675 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 676 | Third extends TypeValidator | CoerceableToTypeValidator | unknown 677 | >( 678 | first: First, 679 | second: Second, 680 | third: Third 681 | ): TypeValidator< 682 | UnwrapTypeFromCoerceableOrValidator & 683 | UnwrapTypeFromCoerceableOrValidator & 684 | UnwrapTypeFromCoerceableOrValidator 685 | >; 686 | < 687 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 688 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 689 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 690 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown 691 | >( 692 | first: First, 693 | second: Second, 694 | third: Third, 695 | fourth: Fourth 696 | ): TypeValidator< 697 | UnwrapTypeFromCoerceableOrValidator & 698 | UnwrapTypeFromCoerceableOrValidator & 699 | UnwrapTypeFromCoerceableOrValidator & 700 | UnwrapTypeFromCoerceableOrValidator 701 | >; 702 | < 703 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 704 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 705 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 706 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 707 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown 708 | >( 709 | first: First, 710 | second: Second, 711 | third: Third, 712 | fourth: Fourth, 713 | fifth: Fifth 714 | ): TypeValidator< 715 | UnwrapTypeFromCoerceableOrValidator & 716 | UnwrapTypeFromCoerceableOrValidator & 717 | UnwrapTypeFromCoerceableOrValidator & 718 | UnwrapTypeFromCoerceableOrValidator & 719 | UnwrapTypeFromCoerceableOrValidator 720 | >; 721 | < 722 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 723 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 724 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 725 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 726 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 727 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown 728 | >( 729 | first: First, 730 | second: Second, 731 | third: Third, 732 | fourth: Fourth, 733 | fifth: Fifth, 734 | sixth: Sixth 735 | ): TypeValidator< 736 | UnwrapTypeFromCoerceableOrValidator & 737 | UnwrapTypeFromCoerceableOrValidator & 738 | UnwrapTypeFromCoerceableOrValidator & 739 | UnwrapTypeFromCoerceableOrValidator & 740 | UnwrapTypeFromCoerceableOrValidator & 741 | UnwrapTypeFromCoerceableOrValidator 742 | >; 743 | < 744 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 745 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 746 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 747 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 748 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 749 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 750 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown 751 | >( 752 | first: First, 753 | second: Second, 754 | third: Third, 755 | fourth: Fourth, 756 | fifth: Fifth, 757 | sixth: Sixth, 758 | seventh: Seventh 759 | ): TypeValidator< 760 | UnwrapTypeFromCoerceableOrValidator & 761 | UnwrapTypeFromCoerceableOrValidator & 762 | UnwrapTypeFromCoerceableOrValidator & 763 | UnwrapTypeFromCoerceableOrValidator & 764 | UnwrapTypeFromCoerceableOrValidator & 765 | UnwrapTypeFromCoerceableOrValidator & 766 | UnwrapTypeFromCoerceableOrValidator 767 | >; 768 | < 769 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 770 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 771 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 772 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 773 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 774 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 775 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 776 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown 777 | >( 778 | first: First, 779 | second: Second, 780 | third: Third, 781 | fourth: Fourth, 782 | fifth: Fifth, 783 | sixth: Sixth, 784 | seventh: Seventh, 785 | eighth: Eighth 786 | ): TypeValidator< 787 | UnwrapTypeFromCoerceableOrValidator & 788 | UnwrapTypeFromCoerceableOrValidator & 789 | UnwrapTypeFromCoerceableOrValidator & 790 | UnwrapTypeFromCoerceableOrValidator & 791 | UnwrapTypeFromCoerceableOrValidator & 792 | UnwrapTypeFromCoerceableOrValidator & 793 | UnwrapTypeFromCoerceableOrValidator & 794 | UnwrapTypeFromCoerceableOrValidator 795 | >; 796 | < 797 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 798 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 799 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 800 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 801 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 802 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 803 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 804 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown, 805 | Ninth extends TypeValidator | CoerceableToTypeValidator | unknown 806 | >( 807 | first: First, 808 | second: Second, 809 | third: Third, 810 | fourth: Fourth, 811 | fifth: Fifth, 812 | sixth: Sixth, 813 | seventh: Seventh, 814 | eighth: Eighth, 815 | ninth: Ninth 816 | ): TypeValidator< 817 | UnwrapTypeFromCoerceableOrValidator & 818 | UnwrapTypeFromCoerceableOrValidator & 819 | UnwrapTypeFromCoerceableOrValidator & 820 | UnwrapTypeFromCoerceableOrValidator & 821 | UnwrapTypeFromCoerceableOrValidator & 822 | UnwrapTypeFromCoerceableOrValidator & 823 | UnwrapTypeFromCoerceableOrValidator & 824 | UnwrapTypeFromCoerceableOrValidator & 825 | UnwrapTypeFromCoerceableOrValidator 826 | >; 827 | < 828 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 829 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 830 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 831 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 832 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 833 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 834 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 835 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown, 836 | Ninth extends TypeValidator | CoerceableToTypeValidator | unknown, 837 | Tenth extends TypeValidator | CoerceableToTypeValidator | unknown 838 | >( 839 | first: First, 840 | second: Second, 841 | third: Third, 842 | fourth: Fourth, 843 | fifth: Fifth, 844 | sixth: Sixth, 845 | seventh: Seventh, 846 | eighth: Eighth, 847 | ninth: Ninth, 848 | tenth: Tenth 849 | ): TypeValidator< 850 | UnwrapTypeFromCoerceableOrValidator & 851 | UnwrapTypeFromCoerceableOrValidator & 852 | UnwrapTypeFromCoerceableOrValidator & 853 | UnwrapTypeFromCoerceableOrValidator & 854 | UnwrapTypeFromCoerceableOrValidator & 855 | UnwrapTypeFromCoerceableOrValidator & 856 | UnwrapTypeFromCoerceableOrValidator & 857 | UnwrapTypeFromCoerceableOrValidator & 858 | UnwrapTypeFromCoerceableOrValidator & 859 | UnwrapTypeFromCoerceableOrValidator 860 | >; 861 | }; 862 | union: { 863 | < 864 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 865 | Second extends TypeValidator | CoerceableToTypeValidator | unknown 866 | >( 867 | first: First, 868 | second: Second 869 | ): TypeValidator< 870 | | UnwrapTypeFromCoerceableOrValidator 871 | | UnwrapTypeFromCoerceableOrValidator 872 | >; 873 | < 874 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 875 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 876 | Third extends TypeValidator | CoerceableToTypeValidator | unknown 877 | >( 878 | first: First, 879 | second: Second, 880 | third: Third 881 | ): TypeValidator< 882 | | UnwrapTypeFromCoerceableOrValidator 883 | | UnwrapTypeFromCoerceableOrValidator 884 | | UnwrapTypeFromCoerceableOrValidator 885 | >; 886 | < 887 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 888 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 889 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 890 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown 891 | >( 892 | first: First, 893 | second: Second, 894 | third: Third, 895 | fourth: Fourth 896 | ): TypeValidator< 897 | | UnwrapTypeFromCoerceableOrValidator 898 | | UnwrapTypeFromCoerceableOrValidator 899 | | UnwrapTypeFromCoerceableOrValidator 900 | | UnwrapTypeFromCoerceableOrValidator 901 | >; 902 | < 903 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 904 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 905 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 906 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 907 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown 908 | >( 909 | first: First, 910 | second: Second, 911 | third: Third, 912 | fourth: Fourth, 913 | fifth: Fifth 914 | ): TypeValidator< 915 | | UnwrapTypeFromCoerceableOrValidator 916 | | UnwrapTypeFromCoerceableOrValidator 917 | | UnwrapTypeFromCoerceableOrValidator 918 | | UnwrapTypeFromCoerceableOrValidator 919 | | UnwrapTypeFromCoerceableOrValidator 920 | >; 921 | < 922 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 923 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 924 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 925 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 926 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 927 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown 928 | >( 929 | first: First, 930 | second: Second, 931 | third: Third, 932 | fourth: Fourth, 933 | fifth: Fifth, 934 | sixth: Sixth 935 | ): TypeValidator< 936 | | UnwrapTypeFromCoerceableOrValidator 937 | | UnwrapTypeFromCoerceableOrValidator 938 | | UnwrapTypeFromCoerceableOrValidator 939 | | UnwrapTypeFromCoerceableOrValidator 940 | | UnwrapTypeFromCoerceableOrValidator 941 | | UnwrapTypeFromCoerceableOrValidator 942 | >; 943 | < 944 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 945 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 946 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 947 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 948 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 949 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 950 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown 951 | >( 952 | first: First, 953 | second: Second, 954 | third: Third, 955 | fourth: Fourth, 956 | fifth: Fifth, 957 | sixth: Sixth, 958 | seventh: Seventh 959 | ): TypeValidator< 960 | | UnwrapTypeFromCoerceableOrValidator 961 | | UnwrapTypeFromCoerceableOrValidator 962 | | UnwrapTypeFromCoerceableOrValidator 963 | | UnwrapTypeFromCoerceableOrValidator 964 | | UnwrapTypeFromCoerceableOrValidator 965 | | UnwrapTypeFromCoerceableOrValidator 966 | | UnwrapTypeFromCoerceableOrValidator 967 | >; 968 | < 969 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 970 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 971 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 972 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 973 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 974 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 975 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 976 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown 977 | >( 978 | first: First, 979 | second: Second, 980 | third: Third, 981 | fourth: Fourth, 982 | fifth: Fifth, 983 | sixth: Sixth, 984 | seventh: Seventh, 985 | eighth: Eighth 986 | ): TypeValidator< 987 | | UnwrapTypeFromCoerceableOrValidator 988 | | UnwrapTypeFromCoerceableOrValidator 989 | | UnwrapTypeFromCoerceableOrValidator 990 | | UnwrapTypeFromCoerceableOrValidator 991 | | UnwrapTypeFromCoerceableOrValidator 992 | | UnwrapTypeFromCoerceableOrValidator 993 | | UnwrapTypeFromCoerceableOrValidator 994 | | UnwrapTypeFromCoerceableOrValidator 995 | >; 996 | < 997 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 998 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 999 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1000 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1001 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 1002 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 1003 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 1004 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown, 1005 | Ninth extends TypeValidator | CoerceableToTypeValidator | unknown 1006 | >( 1007 | first: First, 1008 | second: Second, 1009 | third: Third, 1010 | fourth: Fourth, 1011 | fifth: Fifth, 1012 | sixth: Sixth, 1013 | seventh: Seventh, 1014 | eighth: Eighth, 1015 | ninth: Ninth 1016 | ): TypeValidator< 1017 | | UnwrapTypeFromCoerceableOrValidator 1018 | | UnwrapTypeFromCoerceableOrValidator 1019 | | UnwrapTypeFromCoerceableOrValidator 1020 | | UnwrapTypeFromCoerceableOrValidator 1021 | | UnwrapTypeFromCoerceableOrValidator 1022 | | UnwrapTypeFromCoerceableOrValidator 1023 | | UnwrapTypeFromCoerceableOrValidator 1024 | | UnwrapTypeFromCoerceableOrValidator 1025 | | UnwrapTypeFromCoerceableOrValidator 1026 | >; 1027 | < 1028 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1029 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1030 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1031 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1032 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 1033 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 1034 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 1035 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown, 1036 | Ninth extends TypeValidator | CoerceableToTypeValidator | unknown, 1037 | Tenth extends TypeValidator | CoerceableToTypeValidator | unknown 1038 | >( 1039 | first: First, 1040 | second: Second, 1041 | third: Third, 1042 | fourth: Fourth, 1043 | fifth: Fifth, 1044 | sixth: Sixth, 1045 | seventh: Seventh, 1046 | eighth: Eighth, 1047 | ninth: Ninth, 1048 | tenth: Tenth 1049 | ): TypeValidator< 1050 | | UnwrapTypeFromCoerceableOrValidator 1051 | | UnwrapTypeFromCoerceableOrValidator 1052 | | UnwrapTypeFromCoerceableOrValidator 1053 | | UnwrapTypeFromCoerceableOrValidator 1054 | | UnwrapTypeFromCoerceableOrValidator 1055 | | UnwrapTypeFromCoerceableOrValidator 1056 | | UnwrapTypeFromCoerceableOrValidator 1057 | | UnwrapTypeFromCoerceableOrValidator 1058 | | UnwrapTypeFromCoerceableOrValidator 1059 | | UnwrapTypeFromCoerceableOrValidator 1060 | >; 1061 | }; 1062 | or: { 1063 | < 1064 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1065 | Second extends TypeValidator | CoerceableToTypeValidator | unknown 1066 | >( 1067 | first: First, 1068 | second: Second 1069 | ): TypeValidator< 1070 | | UnwrapTypeFromCoerceableOrValidator 1071 | | UnwrapTypeFromCoerceableOrValidator 1072 | >; 1073 | < 1074 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1075 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1076 | Third extends TypeValidator | CoerceableToTypeValidator | unknown 1077 | >( 1078 | first: First, 1079 | second: Second, 1080 | third: Third 1081 | ): TypeValidator< 1082 | | UnwrapTypeFromCoerceableOrValidator 1083 | | UnwrapTypeFromCoerceableOrValidator 1084 | | UnwrapTypeFromCoerceableOrValidator 1085 | >; 1086 | < 1087 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1088 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1089 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1090 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown 1091 | >( 1092 | first: First, 1093 | second: Second, 1094 | third: Third, 1095 | fourth: Fourth 1096 | ): TypeValidator< 1097 | | UnwrapTypeFromCoerceableOrValidator 1098 | | UnwrapTypeFromCoerceableOrValidator 1099 | | UnwrapTypeFromCoerceableOrValidator 1100 | | UnwrapTypeFromCoerceableOrValidator 1101 | >; 1102 | < 1103 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1104 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1105 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1106 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1107 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown 1108 | >( 1109 | first: First, 1110 | second: Second, 1111 | third: Third, 1112 | fourth: Fourth, 1113 | fifth: Fifth 1114 | ): TypeValidator< 1115 | | UnwrapTypeFromCoerceableOrValidator 1116 | | UnwrapTypeFromCoerceableOrValidator 1117 | | UnwrapTypeFromCoerceableOrValidator 1118 | | UnwrapTypeFromCoerceableOrValidator 1119 | | UnwrapTypeFromCoerceableOrValidator 1120 | >; 1121 | < 1122 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1123 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1124 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1125 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1126 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 1127 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown 1128 | >( 1129 | first: First, 1130 | second: Second, 1131 | third: Third, 1132 | fourth: Fourth, 1133 | fifth: Fifth, 1134 | sixth: Sixth 1135 | ): TypeValidator< 1136 | | UnwrapTypeFromCoerceableOrValidator 1137 | | UnwrapTypeFromCoerceableOrValidator 1138 | | UnwrapTypeFromCoerceableOrValidator 1139 | | UnwrapTypeFromCoerceableOrValidator 1140 | | UnwrapTypeFromCoerceableOrValidator 1141 | | UnwrapTypeFromCoerceableOrValidator 1142 | >; 1143 | < 1144 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1145 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1146 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1147 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1148 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 1149 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 1150 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown 1151 | >( 1152 | first: First, 1153 | second: Second, 1154 | third: Third, 1155 | fourth: Fourth, 1156 | fifth: Fifth, 1157 | sixth: Sixth, 1158 | seventh: Seventh 1159 | ): TypeValidator< 1160 | | UnwrapTypeFromCoerceableOrValidator 1161 | | UnwrapTypeFromCoerceableOrValidator 1162 | | UnwrapTypeFromCoerceableOrValidator 1163 | | UnwrapTypeFromCoerceableOrValidator 1164 | | UnwrapTypeFromCoerceableOrValidator 1165 | | UnwrapTypeFromCoerceableOrValidator 1166 | | UnwrapTypeFromCoerceableOrValidator 1167 | >; 1168 | < 1169 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1170 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1171 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1172 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1173 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 1174 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 1175 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 1176 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown 1177 | >( 1178 | first: First, 1179 | second: Second, 1180 | third: Third, 1181 | fourth: Fourth, 1182 | fifth: Fifth, 1183 | sixth: Sixth, 1184 | seventh: Seventh, 1185 | eighth: Eighth 1186 | ): TypeValidator< 1187 | | UnwrapTypeFromCoerceableOrValidator 1188 | | UnwrapTypeFromCoerceableOrValidator 1189 | | UnwrapTypeFromCoerceableOrValidator 1190 | | UnwrapTypeFromCoerceableOrValidator 1191 | | UnwrapTypeFromCoerceableOrValidator 1192 | | UnwrapTypeFromCoerceableOrValidator 1193 | | UnwrapTypeFromCoerceableOrValidator 1194 | | UnwrapTypeFromCoerceableOrValidator 1195 | >; 1196 | < 1197 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1198 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1199 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1200 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1201 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 1202 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 1203 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 1204 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown, 1205 | Ninth extends TypeValidator | CoerceableToTypeValidator | unknown 1206 | >( 1207 | first: First, 1208 | second: Second, 1209 | third: Third, 1210 | fourth: Fourth, 1211 | fifth: Fifth, 1212 | sixth: Sixth, 1213 | seventh: Seventh, 1214 | eighth: Eighth, 1215 | ninth: Ninth 1216 | ): TypeValidator< 1217 | | UnwrapTypeFromCoerceableOrValidator 1218 | | UnwrapTypeFromCoerceableOrValidator 1219 | | UnwrapTypeFromCoerceableOrValidator 1220 | | UnwrapTypeFromCoerceableOrValidator 1221 | | UnwrapTypeFromCoerceableOrValidator 1222 | | UnwrapTypeFromCoerceableOrValidator 1223 | | UnwrapTypeFromCoerceableOrValidator 1224 | | UnwrapTypeFromCoerceableOrValidator 1225 | | UnwrapTypeFromCoerceableOrValidator 1226 | >; 1227 | < 1228 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1229 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1230 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1231 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1232 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 1233 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 1234 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 1235 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown, 1236 | Ninth extends TypeValidator | CoerceableToTypeValidator | unknown, 1237 | Tenth extends TypeValidator | CoerceableToTypeValidator | unknown 1238 | >( 1239 | first: First, 1240 | second: Second, 1241 | third: Third, 1242 | fourth: Fourth, 1243 | fifth: Fifth, 1244 | sixth: Sixth, 1245 | seventh: Seventh, 1246 | eighth: Eighth, 1247 | ninth: Ninth, 1248 | tenth: Tenth 1249 | ): TypeValidator< 1250 | | UnwrapTypeFromCoerceableOrValidator 1251 | | UnwrapTypeFromCoerceableOrValidator 1252 | | UnwrapTypeFromCoerceableOrValidator 1253 | | UnwrapTypeFromCoerceableOrValidator 1254 | | UnwrapTypeFromCoerceableOrValidator 1255 | | UnwrapTypeFromCoerceableOrValidator 1256 | | UnwrapTypeFromCoerceableOrValidator 1257 | | UnwrapTypeFromCoerceableOrValidator 1258 | | UnwrapTypeFromCoerceableOrValidator 1259 | | UnwrapTypeFromCoerceableOrValidator 1260 | >; 1261 | }; 1262 | mapOf< 1263 | K extends TypeValidator | CoerceableToTypeValidator | unknown, 1264 | V extends TypeValidator | CoerceableToTypeValidator | unknown 1265 | >( 1266 | keyType: K, 1267 | valueType: V 1268 | ): TypeValidator< 1269 | Map< 1270 | UnwrapTypeFromCoerceableOrValidator, 1271 | UnwrapTypeFromCoerceableOrValidator 1272 | > 1273 | >; 1274 | setOf | CoerceableToTypeValidator | unknown>( 1275 | itemType: T 1276 | ): TypeValidator>>; 1277 | maybe | CoerceableToTypeValidator | unknown>( 1278 | itemType: T 1279 | ): TypeValidator | undefined | null>; 1280 | objectWithProperties< 1281 | T extends { 1282 | [key: string | number | symbol]: 1283 | | TypeValidator 1284 | | CoerceableToTypeValidator 1285 | | unknown; 1286 | } 1287 | >( 1288 | properties: T 1289 | ): TypeValidator<{ 1290 | [key in keyof T]: UnwrapTypeFromCoerceableOrValidator; 1291 | }>; 1292 | objectWithOnlyTheseProperties< 1293 | T extends { 1294 | [key: string | number | symbol]: 1295 | | TypeValidator 1296 | | CoerceableToTypeValidator 1297 | | unknown; 1298 | } 1299 | >( 1300 | properties: T 1301 | ): TypeValidator<{ 1302 | [key in keyof T]: UnwrapTypeFromCoerceableOrValidator; 1303 | }>; 1304 | 1305 | mappingObjectOf< 1306 | Values extends TypeValidator | CoerceableToTypeValidator | unknown, 1307 | Keys extends TypeValidator | CoerceableToTypeValidator | unknown 1308 | >( 1309 | keyType: Keys, 1310 | valueType: Values 1311 | ): TypeValidator< 1312 | Record< 1313 | UnwrapTypeFromCoerceableOrValidator extends string | number | symbol 1314 | ? UnwrapTypeFromCoerceableOrValidator 1315 | : never, 1316 | UnwrapTypeFromCoerceableOrValidator 1317 | > 1318 | >; 1319 | record< 1320 | Values extends TypeValidator | CoerceableToTypeValidator | unknown, 1321 | Keys extends TypeValidator | CoerceableToTypeValidator | unknown 1322 | >( 1323 | keyType: Keys, 1324 | valueType: Values 1325 | ): TypeValidator< 1326 | Record< 1327 | UnwrapTypeFromCoerceableOrValidator extends string | number | symbol 1328 | ? UnwrapTypeFromCoerceableOrValidator 1329 | : never, 1330 | UnwrapTypeFromCoerceableOrValidator 1331 | > 1332 | >; 1333 | partialObjectWithProperties< 1334 | T extends { 1335 | [key: string | number | symbol]: 1336 | | TypeValidator 1337 | | CoerceableToTypeValidator 1338 | | unknown; 1339 | } 1340 | >( 1341 | properties: T 1342 | ): TypeValidator<{ 1343 | [key in keyof T]: 1344 | | UnwrapTypeFromCoerceableOrValidator 1345 | | null 1346 | | undefined; 1347 | }>; 1348 | tuple: { 1349 | < 1350 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1351 | Second extends TypeValidator | CoerceableToTypeValidator | unknown 1352 | >( 1353 | first: First, 1354 | second: Second 1355 | ): TypeValidator< 1356 | [ 1357 | UnwrapTypeFromCoerceableOrValidator, 1358 | UnwrapTypeFromCoerceableOrValidator 1359 | ] 1360 | >; 1361 | < 1362 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1363 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1364 | Third extends TypeValidator | CoerceableToTypeValidator | unknown 1365 | >( 1366 | first: First, 1367 | second: Second, 1368 | third: Third 1369 | ): TypeValidator< 1370 | [ 1371 | UnwrapTypeFromCoerceableOrValidator, 1372 | UnwrapTypeFromCoerceableOrValidator, 1373 | UnwrapTypeFromCoerceableOrValidator 1374 | ] 1375 | >; 1376 | < 1377 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1378 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1379 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1380 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown 1381 | >( 1382 | first: First, 1383 | second: Second, 1384 | third: Third, 1385 | fourth: Fourth 1386 | ): TypeValidator< 1387 | [ 1388 | UnwrapTypeFromCoerceableOrValidator, 1389 | UnwrapTypeFromCoerceableOrValidator, 1390 | UnwrapTypeFromCoerceableOrValidator, 1391 | UnwrapTypeFromCoerceableOrValidator 1392 | ] 1393 | >; 1394 | < 1395 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1396 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1397 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1398 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1399 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown 1400 | >( 1401 | first: First, 1402 | second: Second, 1403 | third: Third, 1404 | fourth: Fourth, 1405 | fifth: Fifth 1406 | ): TypeValidator< 1407 | [ 1408 | UnwrapTypeFromCoerceableOrValidator, 1409 | UnwrapTypeFromCoerceableOrValidator, 1410 | UnwrapTypeFromCoerceableOrValidator, 1411 | UnwrapTypeFromCoerceableOrValidator, 1412 | UnwrapTypeFromCoerceableOrValidator 1413 | ] 1414 | >; 1415 | < 1416 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1417 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1418 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1419 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1420 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 1421 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown 1422 | >( 1423 | first: First, 1424 | second: Second, 1425 | third: Third, 1426 | fourth: Fourth, 1427 | fifth: Fifth, 1428 | sixth: Sixth 1429 | ): TypeValidator< 1430 | [ 1431 | UnwrapTypeFromCoerceableOrValidator, 1432 | UnwrapTypeFromCoerceableOrValidator, 1433 | UnwrapTypeFromCoerceableOrValidator, 1434 | UnwrapTypeFromCoerceableOrValidator, 1435 | UnwrapTypeFromCoerceableOrValidator, 1436 | UnwrapTypeFromCoerceableOrValidator 1437 | ] 1438 | >; 1439 | < 1440 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1441 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1442 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1443 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1444 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 1445 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 1446 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown 1447 | >( 1448 | first: First, 1449 | second: Second, 1450 | third: Third, 1451 | fourth: Fourth, 1452 | fifth: Fifth, 1453 | sixth: Sixth, 1454 | seventh: Seventh 1455 | ): TypeValidator< 1456 | [ 1457 | UnwrapTypeFromCoerceableOrValidator, 1458 | UnwrapTypeFromCoerceableOrValidator, 1459 | UnwrapTypeFromCoerceableOrValidator, 1460 | UnwrapTypeFromCoerceableOrValidator, 1461 | UnwrapTypeFromCoerceableOrValidator, 1462 | UnwrapTypeFromCoerceableOrValidator, 1463 | UnwrapTypeFromCoerceableOrValidator 1464 | ] 1465 | >; 1466 | < 1467 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1468 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1469 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1470 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1471 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 1472 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 1473 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 1474 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown 1475 | >( 1476 | first: First, 1477 | second: Second, 1478 | third: Third, 1479 | fourth: Fourth, 1480 | fifth: Fifth, 1481 | sixth: Sixth, 1482 | seventh: Seventh, 1483 | eighth: Eighth 1484 | ): TypeValidator< 1485 | [ 1486 | UnwrapTypeFromCoerceableOrValidator, 1487 | UnwrapTypeFromCoerceableOrValidator, 1488 | UnwrapTypeFromCoerceableOrValidator, 1489 | UnwrapTypeFromCoerceableOrValidator, 1490 | UnwrapTypeFromCoerceableOrValidator, 1491 | UnwrapTypeFromCoerceableOrValidator, 1492 | UnwrapTypeFromCoerceableOrValidator, 1493 | UnwrapTypeFromCoerceableOrValidator 1494 | ] 1495 | >; 1496 | < 1497 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1498 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1499 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1500 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1501 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 1502 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 1503 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 1504 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown, 1505 | Ninth extends TypeValidator | CoerceableToTypeValidator | unknown 1506 | >( 1507 | first: First, 1508 | second: Second, 1509 | third: Third, 1510 | fourth: Fourth, 1511 | fifth: Fifth, 1512 | sixth: Sixth, 1513 | seventh: Seventh, 1514 | eighth: Eighth, 1515 | ninth: Ninth 1516 | ): TypeValidator< 1517 | [ 1518 | UnwrapTypeFromCoerceableOrValidator, 1519 | UnwrapTypeFromCoerceableOrValidator, 1520 | UnwrapTypeFromCoerceableOrValidator, 1521 | UnwrapTypeFromCoerceableOrValidator, 1522 | UnwrapTypeFromCoerceableOrValidator, 1523 | UnwrapTypeFromCoerceableOrValidator, 1524 | UnwrapTypeFromCoerceableOrValidator, 1525 | UnwrapTypeFromCoerceableOrValidator, 1526 | UnwrapTypeFromCoerceableOrValidator 1527 | ] 1528 | >; 1529 | < 1530 | First extends TypeValidator | CoerceableToTypeValidator | unknown, 1531 | Second extends TypeValidator | CoerceableToTypeValidator | unknown, 1532 | Third extends TypeValidator | CoerceableToTypeValidator | unknown, 1533 | Fourth extends TypeValidator | CoerceableToTypeValidator | unknown, 1534 | Fifth extends TypeValidator | CoerceableToTypeValidator | unknown, 1535 | Sixth extends TypeValidator | CoerceableToTypeValidator | unknown, 1536 | Seventh extends TypeValidator | CoerceableToTypeValidator | unknown, 1537 | Eighth extends TypeValidator | CoerceableToTypeValidator | unknown, 1538 | Ninth extends TypeValidator | CoerceableToTypeValidator | unknown, 1539 | Tenth extends TypeValidator | CoerceableToTypeValidator | unknown 1540 | >( 1541 | first: First, 1542 | second: Second, 1543 | third: Third, 1544 | fourth: Fourth, 1545 | fifth: Fifth, 1546 | sixth: Sixth, 1547 | seventh: Seventh, 1548 | eighth: Eighth, 1549 | ninth: Ninth, 1550 | tenth: Tenth 1551 | ): TypeValidator< 1552 | [ 1553 | UnwrapTypeFromCoerceableOrValidator, 1554 | UnwrapTypeFromCoerceableOrValidator, 1555 | UnwrapTypeFromCoerceableOrValidator, 1556 | UnwrapTypeFromCoerceableOrValidator, 1557 | UnwrapTypeFromCoerceableOrValidator, 1558 | UnwrapTypeFromCoerceableOrValidator, 1559 | UnwrapTypeFromCoerceableOrValidator, 1560 | UnwrapTypeFromCoerceableOrValidator, 1561 | UnwrapTypeFromCoerceableOrValidator, 1562 | UnwrapTypeFromCoerceableOrValidator 1563 | ] 1564 | >; 1565 | }; 1566 | 1567 | coerce: | unknown>( 1568 | value: V 1569 | ) => TypeValidator>; 1570 | 1571 | FILE: TypeValidator; 1572 | Path: TypeValidator; 1573 | JSX: { 1574 | unknownElement: TypeValidator< 1575 | JSX.Element<{ [key: string | symbol | number]: unknown }, unknown> 1576 | >; 1577 | anyElement: TypeValidator>; 1578 | Element: TypeValidator< 1579 | JSX.Element<{ [key: string | symbol | number]: unknown }, unknown> 1580 | >; 1581 | Fragment: TypeValidator; 1582 | }; 1583 | }; 1584 | 1585 | `, 1586 | ); 1587 | const a2 = clone(a); 1588 | 1589 | expect(a2).toBeInstanceOf(a.constructor); 1590 | expect(util.inspect(a)).toBe(util.inspect(a2)); 1591 | }); 1592 | 1593 | test("hasShape", async () => { 1594 | { 1595 | const input = { type: "Bla", one: [1] }; 1596 | const shape = { type: "Bla" }; 1597 | expect(hasShape(input, shape)).toBe(true); 1598 | } 1599 | 1600 | { 1601 | const input = { type: "Bla", one: [1] }; 1602 | const shape = { type: "Blah" }; 1603 | expect(hasShape(input, shape)).toBe(false); 1604 | } 1605 | 1606 | { 1607 | const input = [1, 2, 3]; 1608 | const shape = [1, 2]; 1609 | expect(hasShape(input, shape)).toBe(true); 1610 | } 1611 | 1612 | { 1613 | const input = [1, 2, 3]; 1614 | const shape = [2, 2]; 1615 | expect(hasShape(input, shape)).toBe(false); 1616 | } 1617 | 1618 | { 1619 | const input = [1, 2, 3]; 1620 | const shape = [1, 2, 3, 4]; 1621 | expect(hasShape(input, shape)).toBe(false); 1622 | } 1623 | 1624 | { 1625 | const input = 4; 1626 | const shape = 4; 1627 | expect(hasShape(input, shape)).toBe(true); 1628 | } 1629 | 1630 | { 1631 | const input = "hi"; 1632 | const shape = "hiiii"; 1633 | expect(hasShape(input, shape)).toBe(false); 1634 | } 1635 | 1636 | { 1637 | const input = "hi"; 1638 | const shape = "hi"; 1639 | expect(hasShape(input, shape)).toBe(true); 1640 | } 1641 | 1642 | { 1643 | const input = parse("hi;"); 1644 | const shape = { type: "File", program: { type: "Program" } }; 1645 | expect(hasShape(input, shape)).toBe(true); 1646 | } 1647 | 1648 | { 1649 | const input = parse("hi;"); 1650 | const shape = { type: "File", program: { type: "Not a Program" } }; 1651 | expect(hasShape(input, shape)).toBe(false); 1652 | } 1653 | 1654 | { 1655 | const input = parse("hi;") as types.Node; 1656 | 1657 | const check = (_: types.File) => {}; 1658 | 1659 | // @ts-expect-error input is not types.File 1660 | check(input); 1661 | 1662 | if (hasShape(input, { type: "File" } as const)) { 1663 | // There should not be a typescript error on the next line 1664 | check(input); 1665 | } 1666 | } 1667 | }); 1668 | 1669 | test("print with non-file (simple)", () => { 1670 | const ast = parse("hi;"); 1671 | const node = (ast as any).program.body[0].expression; 1672 | const result = print(node); 1673 | expect(result).toMatchInlineSnapshot(` 1674 | { 1675 | "code": "hi", 1676 | "map": null, 1677 | } 1678 | `); 1679 | }); 1680 | 1681 | test("print with non-file (complicated)", () => { 1682 | const ast = parse( 1683 | " console.log( hi, 2 + some `one ${\noutThere}\n \t \t` ) ;\n\tvar theBite = 87;", 1684 | ) as types.File; 1685 | 1686 | const callExpression: types.CallExpression = (ast.program.body[0] as any) 1687 | .expression; 1688 | 1689 | const hi: types.Identifier = callExpression.arguments[0] as any; 1690 | 1691 | const binaryExpression: types.BinaryExpression = callExpression 1692 | .arguments[1] as any; 1693 | 1694 | const variableDeclaration: types.VariableDeclaration = ast.program 1695 | .body[1] as any; 1696 | 1697 | const theBite: types.Identifier = variableDeclaration.declarations[0] 1698 | .init as any; 1699 | 1700 | const result = { 1701 | callExpression: print(callExpression), 1702 | hi: print(hi), 1703 | binaryExpression: print(binaryExpression), 1704 | variableDeclaration: print(variableDeclaration), 1705 | theBite: print(theBite), 1706 | }; 1707 | 1708 | expect(result).toMatchInlineSnapshot(` 1709 | { 1710 | "binaryExpression": { 1711 | "code": "2 + some \`one \${ 1712 | outThere} 1713 | \`", 1714 | "map": null, 1715 | }, 1716 | "callExpression": { 1717 | "code": "console.log( hi, 2 + some \`one \${ 1718 | outThere} 1719 | \` )", 1720 | "map": null, 1721 | }, 1722 | "hi": { 1723 | "code": "hi", 1724 | "map": null, 1725 | }, 1726 | "theBite": { 1727 | "code": "87", 1728 | "map": null, 1729 | }, 1730 | "variableDeclaration": { 1731 | "code": "var theBite = 87;", 1732 | "map": null, 1733 | }, 1734 | } 1735 | `); 1736 | }); 1737 | 1738 | test("parse as expression", () => { 1739 | const node = parse("hi", { parseOptions: { expression: true } }); 1740 | expect(node.type).toBe("Identifier"); 1741 | expect((node as any).name).toBe("hi"); 1742 | }); 1743 | 1744 | test("parse as expression but it's a statement", () => { 1745 | expect(() => { 1746 | parse("hi;", { parseOptions: { expression: true } }); 1747 | }).toThrowErrorMatchingInlineSnapshot( 1748 | `[SyntaxError: Unexpected token, expected "," (1:3)]`, 1749 | ); 1750 | }); 1751 | 1752 | test("parse function (basic)", () => { 1753 | const node = parse("hi") as any; 1754 | expect(node.type).toBe("File"); 1755 | expect(node.program.body[0].expression.type).toBe("Identifier"); 1756 | expect(node.program.body[0].expression.name).toBe("hi"); 1757 | }); 1758 | 1759 | test("parse function (jsx)", () => { 1760 | const node = parse("") as any; 1761 | expect(node.type).toBe("File"); 1762 | expect(node.program.body[0].expression.type).toBe("JSXElement"); 1763 | }); 1764 | 1765 | test("parse function (typescript)", () => { 1766 | const node = parse("const a = (something: string): string => something;"); 1767 | expect(node.type).toBe("File"); 1768 | expect(node.program.body[0].type).toBe("VariableDeclaration"); 1769 | const decl = node.program.body[0] as any; 1770 | expect(decl.declarations[0].init.type).toBe("ArrowFunctionExpression"); 1771 | const arrowFnExpr = decl.declarations[0].init; 1772 | expect(arrowFnExpr.returnType.typeAnnotation.type).toBe("TSStringKeyword"); 1773 | }); 1774 | 1775 | test("parse function (typescript-dts)", () => { 1776 | // Note: without dts option, this fails with "Missing initializer in const declaration." 1777 | const node = parse("export const a: string;", { 1778 | parseOptions: { 1779 | typeSyntax: "typescript-dts", 1780 | }, 1781 | }); 1782 | expect(node.type).toBe("File"); 1783 | expect(node.program.body[0].type).toBe("ExportNamedDeclaration"); 1784 | const exportDecl = node.program.body[0] as any; 1785 | expect(exportDecl.declaration.type).toBe("VariableDeclaration"); 1786 | const variableDeclaration = exportDecl.declaration; 1787 | expect(variableDeclaration.declarations[0].type).toBe("VariableDeclarator"); 1788 | const variableDeclarator = variableDeclaration.declarations[0]; 1789 | 1790 | // Note: const declaration without init not allowed in non-dts 1791 | expect(variableDeclaration.kind).toBe("const"); 1792 | expect(variableDeclarator.init).toBe(null); 1793 | 1794 | expect(variableDeclarator.id.type).toBe("Identifier"); 1795 | const typeAnnotation = variableDeclarator.id.typeAnnotation; 1796 | expect(typeAnnotation.type).toBe("TSTypeAnnotation"); 1797 | }); 1798 | 1799 | test("parse function (flow)", () => { 1800 | const node = parse("const a = (something: string): string => something;", { 1801 | parseOptions: { 1802 | typeSyntax: "flow", 1803 | }, 1804 | }); 1805 | expect(node.type).toBe("File"); 1806 | expect(node.program.body[0].type).toBe("VariableDeclaration"); 1807 | const decl = node.program.body[0] as any; 1808 | expect(decl.declarations[0].init.type).toBe("ArrowFunctionExpression"); 1809 | const arrowFnExpr = decl.declarations[0].init; 1810 | expect(arrowFnExpr.returnType.typeAnnotation.type).toBe( 1811 | "StringTypeAnnotation", 1812 | ); 1813 | }); 1814 | 1815 | test("parse function (JSX implicitly enabled)", () => { 1816 | expect(() => { 1817 | parse("const a = 45;"); 1818 | }).toThrowErrorMatchingInlineSnapshot( 1819 | `[SyntaxError: Unterminated JSX contents. (1:18)]`, 1820 | ); 1821 | }); 1822 | 1823 | test("parse function (JSX explicitly enabled)", () => { 1824 | expect(() => { 1825 | parse("const a = 45;", { parseOptions: { jsxEnabled: true } }); 1826 | }).toThrowErrorMatchingInlineSnapshot( 1827 | `[SyntaxError: Unterminated JSX contents. (1:18)]`, 1828 | ); 1829 | }); 1830 | 1831 | test("parse function (JSX explicitly disabled)", () => { 1832 | const node = parse("const a = 45;", { 1833 | parseOptions: { jsxEnabled: false }, 1834 | }); 1835 | expect(node.type).toBe("File"); 1836 | expect(node.program.body[0].type).toBe("VariableDeclaration"); 1837 | const decl = node.program.body[0] as any; 1838 | expect(decl.declarations[0].init.type).toBe("TSTypeAssertion"); 1839 | const init = decl.declarations[0].init; 1840 | expect(init.typeAnnotation.type).toBe("TSStringKeyword"); 1841 | expect(init.expression.type).toBe("NumericLiteral"); 1842 | }); 1843 | 1844 | test("parse function (v8Intrinsic enabled without changing pipeline syntax)", () => { 1845 | expect(() => { 1846 | parse("const a = %Something()", { 1847 | parseOptions: { 1848 | v8Intrinsic: true, 1849 | }, 1850 | }); 1851 | }).toThrowErrorMatchingInlineSnapshot( 1852 | `[Error: Babel disallows using both v8Intrinsic and Hack-style pipes together. \`equivalent-exchange\` has hack-style pipeline syntax enabled by default. Either disable the 'v8Intrinsic' option or change the 'pipelineSyntax' option to a different value, such as 'none' (it defaults to 'hack').]`, 1853 | ); 1854 | }); 1855 | 1856 | test("parse function (v8Intrinsic enabled, pipeline syntax disabled)", () => { 1857 | const node = parse("const a = %Something()", { 1858 | parseOptions: { 1859 | v8Intrinsic: true, 1860 | pipelineSyntax: "none", 1861 | }, 1862 | }); 1863 | expect(node.program.body[0].type).toBe("VariableDeclaration"); 1864 | const decl = node.program.body[0] as any; 1865 | expect(decl.declarations[0].init.type).toBe("CallExpression"); 1866 | expect(decl.declarations[0].init.callee.type).toBe("V8IntrinsicIdentifier"); 1867 | expect(decl.declarations[0].init.callee.name).toBe("Something"); 1868 | }); 1869 | 1870 | test("parse function (v8Intrinsic enabled, pipeline syntax changed to fsharp)", () => { 1871 | const node = parse("const a = %Something()", { 1872 | parseOptions: { 1873 | v8Intrinsic: true, 1874 | pipelineSyntax: "fsharp", 1875 | }, 1876 | }); 1877 | expect(node.program.body[0].type).toBe("VariableDeclaration"); 1878 | const decl = node.program.body[0] as any; 1879 | expect(decl.declarations[0].init.type).toBe("CallExpression"); 1880 | expect(decl.declarations[0].init.callee.type).toBe("V8IntrinsicIdentifier"); 1881 | expect(decl.declarations[0].init.callee.name).toBe("Something"); 1882 | }); 1883 | 1884 | test("parse function (placeholders enabled without changing pipeline syntax)", () => { 1885 | expect(() => { 1886 | parse("const a = %%b%%", { 1887 | parseOptions: { 1888 | placeholders: true, 1889 | }, 1890 | }); 1891 | }).toThrowErrorMatchingInlineSnapshot( 1892 | `[Error: Babel disallows using both placeholders and Hack-style pipes together. \`equivalent-exchange\` has hack-style pipeline syntax enabled by default. Either disable the 'placeholders' option or change the 'pipelineSyntax' option to a different value, such as 'none' (it defaults to 'hack').]`, 1893 | ); 1894 | }); 1895 | 1896 | test("parse function (placeholders enabled, pipeline syntax disabled)", () => { 1897 | const node = parse("const a = %%b%%", { 1898 | parseOptions: { 1899 | placeholders: true, 1900 | pipelineSyntax: "none", 1901 | }, 1902 | }); 1903 | expect(node.program.body[0].type).toBe("VariableDeclaration"); 1904 | const decl = node.program.body[0] as any; 1905 | expect(decl.declarations[0].init.type).toBe("Placeholder"); 1906 | const placeholder = decl.declarations[0].init; 1907 | expect(placeholder.name.type).toBe("Identifier"); 1908 | expect(placeholder.name.name).toBe("b"); 1909 | }); 1910 | 1911 | test("parse function (placeholders enabled, pipeline syntax changed to fsharp)", () => { 1912 | const node = parse("const a = %%b%%", { 1913 | parseOptions: { 1914 | placeholders: true, 1915 | pipelineSyntax: "fsharp", 1916 | }, 1917 | }); 1918 | expect(node.program.body[0].type).toBe("VariableDeclaration"); 1919 | const decl = node.program.body[0] as any; 1920 | expect(decl.declarations[0].init.type).toBe("Placeholder"); 1921 | const placeholder = decl.declarations[0].init; 1922 | expect(placeholder.name.type).toBe("Identifier"); 1923 | expect(placeholder.name.name).toBe("b"); 1924 | }); 1925 | 1926 | test("parse function (placeholders enabled, v8 intrinsics enabled)", () => { 1927 | expect(() => { 1928 | parse("const a = %Something(%%b%%)", { 1929 | parseOptions: { 1930 | placeholders: true, 1931 | v8Intrinsic: true, 1932 | }, 1933 | }); 1934 | }).toThrowErrorMatchingInlineSnapshot( 1935 | `[Error: Babel disallows using both v8Intrinsic and placeholders together at the same time. Either disable the 'v8Intrinsic' option or disable the 'placeholders' option.]`, 1936 | ); 1937 | }); 1938 | 1939 | test("print function (basic)", () => { 1940 | const node = types.identifier("hi"); 1941 | const result = print(node); 1942 | expect(result.code).toBe("hi"); 1943 | }); 1944 | 1945 | test("print function (different methods)", () => { 1946 | const node = types.identifier("hi"); 1947 | 1948 | for (const printMethod of [ 1949 | "@babel/generator", 1950 | "recast.print", 1951 | "recast.prettyPrint", 1952 | ] as const) { 1953 | const result = print(node, { printOptions: { printMethod } }); 1954 | expect(result.code).toBe("hi"); 1955 | } 1956 | }); 1957 | 1958 | test("print function (source maps, recast.print)", () => { 1959 | const fileName = "myfile.js"; 1960 | const sourceMapFileName = "myfile.js.map"; 1961 | 1962 | const ast = parse("console.log(3);", { fileName, sourceMapFileName }); 1963 | 1964 | traverse(ast, { 1965 | Identifier(nodePath) { 1966 | const node = nodePath.node; 1967 | if (node.name === "log") { 1968 | nodePath.replaceWith(types.identifier("pog")); 1969 | } 1970 | }, 1971 | }); 1972 | 1973 | const result = print(ast, { 1974 | printOptions: { 1975 | printMethod: "recast.print", 1976 | }, 1977 | fileName, 1978 | sourceMapFileName, 1979 | }); 1980 | expect(result).toMatchInlineSnapshot(` 1981 | { 1982 | "code": "console.pog(3);", 1983 | "map": { 1984 | "file": "myfile.js.map", 1985 | "mappings": "AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC", 1986 | "names": [], 1987 | "sources": [ 1988 | "myfile.js", 1989 | ], 1990 | "sourcesContent": [ 1991 | "console.log(3);", 1992 | ], 1993 | "version": 3, 1994 | }, 1995 | } 1996 | `); 1997 | }); 1998 | 1999 | test("print function (source maps, recast.prettyPrint)", () => { 2000 | const fileName = "myfile.js"; 2001 | const sourceMapFileName = "myfile.js.map"; 2002 | 2003 | const ast = parse("console.log(3);", { fileName, sourceMapFileName }); 2004 | 2005 | traverse(ast, { 2006 | Identifier(nodePath) { 2007 | const node = nodePath.node; 2008 | if (node.name === "log") { 2009 | nodePath.replaceWith(types.identifier("pog")); 2010 | } 2011 | }, 2012 | }); 2013 | 2014 | const result = print(ast, { 2015 | printOptions: { 2016 | printMethod: "recast.prettyPrint", 2017 | }, 2018 | fileName, 2019 | sourceMapFileName, 2020 | }); 2021 | expect(result).toMatchInlineSnapshot(` 2022 | { 2023 | "code": "console.pog(3);", 2024 | "map": null, 2025 | } 2026 | `); 2027 | }); 2028 | 2029 | test("print function (source maps, @babel/generator)", () => { 2030 | const fileName = "myfile.js"; 2031 | const sourceMapFileName = "myfile.js.map"; 2032 | 2033 | const ast = parse("console.log(3);", { fileName, sourceMapFileName }); 2034 | 2035 | traverse(ast, { 2036 | Identifier(nodePath) { 2037 | const node = nodePath.node; 2038 | if (node.name === "log") { 2039 | nodePath.replaceWith(types.identifier("pog")); 2040 | } 2041 | }, 2042 | }); 2043 | 2044 | const result = print(ast, { 2045 | printOptions: { 2046 | printMethod: "@babel/generator", 2047 | }, 2048 | fileName, 2049 | sourceMapFileName, 2050 | }); 2051 | expect(result).toMatchInlineSnapshot(` 2052 | { 2053 | "code": "console.pog(3);", 2054 | "map": { 2055 | "file": undefined, 2056 | "ignoreList": [], 2057 | "mappings": "AAAAA,OAAO,CAAAC,GAAI,CAAC,CAAC,CAAC", 2058 | "names": [ 2059 | "console", 2060 | "pog", 2061 | ], 2062 | "sourceRoot": undefined, 2063 | "sources": [ 2064 | "myfile.js", 2065 | ], 2066 | "sourcesContent": [ 2067 | null, 2068 | ], 2069 | "version": 3, 2070 | }, 2071 | } 2072 | `); 2073 | }); 2074 | 2075 | // This is here to help people upgrade across the breaking change where we 2076 | // changed what print and parse accept. 2077 | test("parse function throws if you pass it parseOptions directly", () => { 2078 | const fileName = "myfile.js"; 2079 | const sourceMapFileName = "myfile.js.map"; 2080 | 2081 | for (const parseOptionsKey of [ 2082 | "typeSyntax", 2083 | "decoratorSyntax", 2084 | "pipelineSyntax", 2085 | "hackPipelineTopicToken", 2086 | "jsxEnabled", 2087 | "v8Intrinsic", 2088 | "placeholders", 2089 | "expression", 2090 | "skipRecast", 2091 | ]) { 2092 | expect(() => { 2093 | parse("console.log(3);", { 2094 | fileName, 2095 | sourceMapFileName, 2096 | [parseOptionsKey]: true, 2097 | }); 2098 | }).toThrowError(); 2099 | } 2100 | }); 2101 | 2102 | // This is here to help people upgrade across the breaking change where we 2103 | // changed what print and parse accept. 2104 | test("print function throws if you pass it printOptions directly", () => { 2105 | const fileName = "myfile.js"; 2106 | const sourceMapFileName = "myfile.js.map"; 2107 | 2108 | const ast = parse("console.log(3);", { fileName, sourceMapFileName }); 2109 | 2110 | traverse(ast, { 2111 | Identifier(nodePath) { 2112 | const node = nodePath.node; 2113 | if (node.name === "log") { 2114 | nodePath.replaceWith(types.identifier("pog")); 2115 | } 2116 | }, 2117 | }); 2118 | 2119 | expect(() => { 2120 | print(ast, { 2121 | printMethod: "@babel/generator", 2122 | fileName, 2123 | sourceMapFileName, 2124 | } as any); 2125 | }).toThrowErrorMatchingInlineSnapshot( 2126 | `[Error: \`print\` function received a legacy PrintOptions, but we want an Options. The following property should be in a sub-object under \`printOptions\`: printMethod]`, 2127 | ); 2128 | }); 2129 | --------------------------------------------------------------------------------