├── .envrc ├── .prettierignore ├── README.md ├── .gitignore ├── tsup.config.ts ├── vitest.config.ts ├── .changeset ├── config.json └── README.md ├── tsconfig.eslint.json ├── test ├── Utils.ts ├── effect-3.0.test.ts ├── Utils.test.ts ├── schema-0.69.test.ts └── schema-0.65.test.ts ├── flake.nix ├── .github ├── actions │ └── setup │ │ └── action.yml └── workflows │ ├── release.yml │ └── check.yml ├── flake.lock ├── tsconfig.json ├── .vscode ├── tasks.json └── settings.json ├── LICENSE ├── scripts └── copy-package-json.ts ├── public ├── codemods │ ├── effect-3.0.ts │ ├── effect-3.0.4.ts │ ├── minor-2.1.ts │ ├── platform-0.49.ts │ ├── minor-2.4.ts │ ├── minor-2.3.ts │ ├── schema-0.69.ts │ └── schema-0.65.ts └── Utils.ts ├── src └── main.ts ├── patches ├── @changesets__assemble-release-plan@6.0.3.patch └── @changesets__get-github-info@0.6.0.patch ├── package.json ├── .eslintrc.cjs └── CHANGELOG.md /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.ts 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dry run 2 | 3 | ```sh 4 | npx jscodeshift -d -p -t ./public/codemods/.ts test//.ts 5 | ``` 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | *.tsbuildinfo 3 | node_modules/ 4 | yarn-error.log 5 | .ultra.cache.json 6 | .DS_Store 7 | tmp/ 8 | build/ 9 | dist/ 10 | .direnv/ 11 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup" 2 | 3 | export default defineConfig({ 4 | entry: ["src/main.ts"], 5 | clean: true, 6 | publicDir: true, 7 | treeshake: "smallest", 8 | external: ["@parcel/watcher"], 9 | }) 10 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from "vite" 3 | 4 | export default defineConfig({ 5 | test: { 6 | include: ["./test/**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], 7 | exclude: [], 8 | globals: true, 9 | coverage: { 10 | provider: "v8", 11 | }, 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "Effect-TS/codemod" }], 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "exactOptionalPropertyTypes": true, 4 | "noEmit": true, 5 | "allowJs": true, 6 | "baseUrl": "." 7 | }, 8 | "include": [ 9 | "./.eslintrc.cjs", 10 | "./src/**/*.ts", 11 | "./test/**/*.ts", 12 | "./public/**/*.ts", 13 | "./vitest.config.ts", 14 | "./tsup.config.ts", 15 | "./scripts/**/*.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /test/Utils.ts: -------------------------------------------------------------------------------- 1 | import type cs from "jscodeshift" 2 | import * as TestUtils from "jscodeshift/src/testUtils" 3 | 4 | export const expectTransformation = (transformer: cs.Transform) => 5 | ( 6 | description: string, 7 | input: string, 8 | output: string, 9 | ) => { 10 | TestUtils.defineInlineTest( 11 | { default: transformer, parser: "ts" }, 12 | {}, 13 | input, 14 | output, 15 | description, 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 4 | }; 5 | outputs = {nixpkgs, ...}: let 6 | forAllSystems = function: 7 | nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed 8 | (system: function nixpkgs.legacyPackages.${system}); 9 | in { 10 | formatter = forAllSystems (pkgs: pkgs.alejandra); 11 | devShells = forAllSystems (pkgs: { 12 | default = pkgs.mkShell { 13 | packages = with pkgs; [ 14 | bun 15 | corepack 16 | deno 17 | nodejs 18 | ]; 19 | }; 20 | }); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Perform standard setup and install dependencies using pnpm. 3 | inputs: 4 | node-version: 5 | description: The version of Node.js to install 6 | required: true 7 | default: 20.12.2 8 | 9 | runs: 10 | using: composite 11 | steps: 12 | - name: Install pnpm 13 | uses: pnpm/action-setup@v3 14 | - name: Install node 15 | uses: actions/setup-node@v4 16 | with: 17 | cache: pnpm 18 | node-version: ${{ inputs.node-version }} 19 | - name: Install dependencies 20 | shell: bash 21 | run: pnpm install 22 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1720955038, 6 | "narHash": "sha256-GaliJqfFwyYxReFywxAa8orCO+EnDq2NK2F+5aSc8vo=", 7 | "owner": "nixos", 8 | "repo": "nixpkgs", 9 | "rev": "aa247c0c90ecf4ae7a032c54fdc21b91ca274062", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "nixos", 14 | "ref": "nixpkgs-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "exactOptionalPropertyTypes": true, 5 | "target": "ES2021", 6 | "noEmit": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "lib": ["es6"], 10 | "sourceMap": false, 11 | "strict": true, 12 | "noImplicitReturns": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "stripInternal": true, 18 | "skipLibCheck": true, 19 | "types": ["vitest/globals"], 20 | "plugins": [{ "name": "@effect/language-service" }] 21 | }, 22 | "include": ["./src", "./public", "./test"] 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | if: github.repository_owner == 'Effect-Ts' 13 | name: Release 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 10 16 | permissions: 17 | contents: write 18 | id-token: write 19 | pull-requests: write 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Install dependencies 23 | uses: ./.github/actions/setup 24 | - run: pnpm build 25 | - name: Create Release Pull Request or Publish 26 | id: changesets 27 | uses: changesets/action@v1 28 | with: 29 | version: pnpm changeset-version 30 | publish: pnpm changeset-publish 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 34 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: [main] 7 | push: 8 | branches: [main] 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | types: 16 | name: Types 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 10 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Install dependencies 22 | uses: ./.github/actions/setup 23 | - run: pnpm check 24 | 25 | lint: 26 | name: Lint 27 | runs-on: ubuntu-latest 28 | timeout-minutes: 10 29 | steps: 30 | - uses: actions/checkout@v4 31 | - name: Install dependencies 32 | uses: ./.github/actions/setup 33 | - run: pnpm lint 34 | 35 | test: 36 | name: Test 37 | runs-on: ubuntu-latest 38 | timeout-minutes: 10 39 | steps: 40 | - uses: actions/checkout@v4 41 | - name: Install dependencies 42 | uses: ./.github/actions/setup 43 | - name: Test 44 | run: pnpm test 45 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "clean", 6 | "type": "shell", 7 | "command": "direnv exec . pnpm run clean", 8 | "isBackground": false, 9 | "problemMatcher": [] 10 | }, 11 | { 12 | "label": "build-watch", 13 | "type": "shell", 14 | "command": "direnv exec . pnpm run build-watch", 15 | "problemMatcher": [ 16 | "$tsc-watch" 17 | ], 18 | "isBackground": true, 19 | "presentation": { 20 | "focus": false, 21 | "panel": "shared", 22 | "group": "dev", 23 | "showReuseMessage": true, 24 | "clear": false 25 | } 26 | }, 27 | { 28 | "label": "build", 29 | "type": "shell", 30 | "command": "direnv exec . pnpm run build", 31 | "problemMatcher": [], 32 | "isBackground": false 33 | }, 34 | { 35 | "label": "test-watch", 36 | "dependsOn": [ 37 | "build-watch" 38 | ], 39 | "type": "shell", 40 | "command": "direnv exec . pnpm run test", 41 | "problemMatcher": [] 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present The Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/copy-package-json.ts: -------------------------------------------------------------------------------- 1 | import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem" 2 | import * as FileSystem from "@effect/platform/FileSystem" 3 | import { Effect, pipe } from "effect" 4 | import * as path from "node:path" 5 | 6 | const read = pipe( 7 | FileSystem.FileSystem, 8 | Effect.flatMap(fileSystem => fileSystem.readFileString("package.json")), 9 | Effect.map(_ => JSON.parse(_)), 10 | Effect.map(json => ({ 11 | name: json.name, 12 | version: json.version, 13 | description: json.description, 14 | bin: { 15 | "effect-codemod": "main.js", 16 | }, 17 | engines: json.engines, 18 | repository: json.repository, 19 | author: json.author, 20 | license: json.license, 21 | bugs: json.bugs, 22 | homepage: json.homepage, 23 | tags: json.tags, 24 | keywords: json.keywords, 25 | dependencies: json.dependencies, 26 | })), 27 | ) 28 | 29 | const pathTo = path.join("dist", "package.json") 30 | 31 | const write = (pkg: object) => 32 | pipe( 33 | FileSystem.FileSystem, 34 | Effect.flatMap(fileSystem => 35 | fileSystem.writeFileString(pathTo, JSON.stringify(pkg, null, 2)) 36 | ), 37 | ) 38 | 39 | const program = pipe( 40 | Effect.sync(() => console.log(`copying package.json to ${pathTo}...`)), 41 | Effect.flatMap(() => read), 42 | Effect.flatMap(write), 43 | Effect.provide(NodeFileSystem.layer), 44 | ) 45 | 46 | Effect.runPromise(program) 47 | -------------------------------------------------------------------------------- /public/codemods/effect-3.0.ts: -------------------------------------------------------------------------------- 1 | import type cs from "jscodeshift" 2 | import * as Utils from "../Utils" 3 | 4 | export default function transformer(file: cs.FileInfo, api: cs.API) { 5 | const j = api.jscodeshift 6 | const root = j(file.source) 7 | 8 | Utils.replaceImportSource( 9 | api, 10 | root, 11 | "effect/ReadonlyArray", 12 | "effect/Array", 13 | ) 14 | 15 | Utils.replaceImportSource( 16 | api, 17 | root, 18 | "effect/ReadonlyRecord", 19 | "effect/Record", 20 | ) 21 | 22 | Utils.replaceNamedImport( 23 | api, 24 | root, 25 | "effect", 26 | "ReadonlyArray", 27 | "Array", 28 | ) 29 | 30 | Utils.replaceNamedImport( 31 | api, 32 | root, 33 | "effect", 34 | "ReadonlyRecord", 35 | "Record", 36 | ) 37 | 38 | Utils.renameMembers(api, root, "Channel", "unit", "void") 39 | Utils.renameMembers(api, root, "Channel", "asUnit", "asVoid") 40 | Utils.renameMembers(api, root, "Effect", "unit", "void") 41 | Utils.renameMembers(api, root, "Effect", "asUnit", "asVoid") 42 | Utils.renameMembers(api, root, "Exit", "unit", "void") 43 | Utils.renameMembers(api, root, "Exit", "asUnit", "asVoid") 44 | Utils.renameMembers(api, root, "Option", "unit", "void") 45 | Utils.renameMembers(api, root, "Option", "asUnit", "asVoid") 46 | Utils.renameMembers(api, root, "Schedule", "asUnit", "asVoid") 47 | Utils.renameMembers(api, root, "STM", "unit", "void") 48 | Utils.renameMembers(api, root, "STM", "asUnit", "asVoid") 49 | Utils.renameMembers(api, root, "Stream", "unit", "void") 50 | 51 | return root.toSource() 52 | } 53 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.preferences.importModuleSpecifier": "relative", 4 | "typescript.enablePromptUseWorkspaceTsdk": true, 5 | "editor.formatOnSave": true, 6 | "eslint.format.enable": true, 7 | "[json]": { 8 | "editor.defaultFormatter": "vscode.json-language-features" 9 | }, 10 | "[markdown]": { 11 | "editor.defaultFormatter": "esbenp.prettier-vscode", 12 | "prettier.semi": false, 13 | "prettier.trailingComma": "none" 14 | }, 15 | "[javascript]": { 16 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 17 | }, 18 | "[javascriptreact]": { 19 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 20 | }, 21 | "[typescript]": { 22 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 23 | }, 24 | "[typescriptreact]": { 25 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 26 | }, 27 | "eslint.validate": ["markdown", "javascript", "typescript"], 28 | "editor.codeActionsOnSave": { 29 | "source.fixAll.eslint": "explicit" 30 | }, 31 | "editor.quickSuggestions": { 32 | "other": true, 33 | "comments": false, 34 | "strings": false 35 | }, 36 | "editor.acceptSuggestionOnCommitCharacter": true, 37 | "editor.acceptSuggestionOnEnter": "on", 38 | "editor.quickSuggestionsDelay": 10, 39 | "editor.suggestOnTriggerCharacters": true, 40 | "editor.tabCompletion": "off", 41 | "editor.suggest.localityBonus": true, 42 | "editor.suggestSelection": "recentlyUsed", 43 | "editor.wordBasedSuggestions": "matchingDocuments", 44 | "editor.parameterHints.enabled": true, 45 | "files.insertFinalNewline": true 46 | } 47 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as Args from "@effect/cli/Args" 4 | import * as Command from "@effect/cli/Command" 5 | import * as Options from "@effect/cli/Options" 6 | import * as NodeContext from "@effect/platform-node/NodeContext" 7 | import * as Array from "effect/Array" 8 | import * as Console from "effect/Console" 9 | import * as Effect from "effect/Effect" 10 | import * as jscodeshift from "jscodeshift/src/Runner" 11 | import * as Fs from "node:fs" 12 | import * as Path from "node:path" 13 | 14 | const codemod = Args.choice( 15 | Array.map( 16 | Fs.readdirSync(`${__dirname}/codemods`), 17 | file => [ 18 | Path.basename(file, ".ts"), 19 | Path.join(`${__dirname}/codemods`, file), 20 | ], 21 | ), 22 | { name: "codemod" }, 23 | ).pipe( 24 | Args.withDescription("The code modification to run"), 25 | ) 26 | 27 | const run = Command.make("codemod", { 28 | codemod, 29 | paths: Args.text({ name: "paths" }).pipe( 30 | Args.repeated, 31 | Args.withDescription("The paths to run the codemod on"), 32 | ), 33 | options: { 34 | dry: Options.boolean("dry-run").pipe( 35 | Options.withAlias("d"), 36 | Options.withDescription("If set, the codemod will not modify any files"), 37 | ), 38 | print: Options.boolean("print").pipe( 39 | Options.withAlias("p"), 40 | Options.withDescription( 41 | "If set, the codemod will print the changes to the console", 42 | ), 43 | ), 44 | }, 45 | }).pipe( 46 | Command.withHandler(({ codemod, options, paths }) => 47 | Effect.promise(() => 48 | jscodeshift.run(codemod, paths, { 49 | ...options, 50 | babel: true, 51 | parser: "ts", 52 | extensions: "ts,tsx", 53 | }) 54 | ) 55 | ), 56 | Command.run({ 57 | name: "Effect Codemods", 58 | version: "0.0.0", 59 | }), 60 | ) 61 | 62 | run(process.argv).pipe( 63 | Effect.provide(NodeContext.layer), 64 | Effect.tapDefect(Console.error), 65 | Effect.runFork, 66 | ) 67 | -------------------------------------------------------------------------------- /patches/@changesets__assemble-release-plan@6.0.3.patch: -------------------------------------------------------------------------------- 1 | diff --git a/dist/changesets-assemble-release-plan.cjs.js b/dist/changesets-assemble-release-plan.cjs.js 2 | index 60427457c887f2d72168fecec83d79088c68e3a4..b48899c83ca2bc5bfa0cbb65e0c098d5bb65fe3d 100644 3 | --- a/dist/changesets-assemble-release-plan.cjs.js 4 | +++ b/dist/changesets-assemble-release-plan.cjs.js 5 | @@ -189,7 +189,7 @@ function determineDependents({ 6 | preInfo, 7 | onlyUpdatePeerDependentsWhenOutOfRange: config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH.onlyUpdatePeerDependentsWhenOutOfRange 8 | })) { 9 | - type = "major"; 10 | + type = "minor"; 11 | } else if ((!releases.has(dependent) || releases.get(dependent).type === "none") && (config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH.updateInternalDependents === "always" || !semverSatisfies__default["default"](incrementVersion(nextRelease, preInfo), versionRange))) { 12 | switch (depType) { 13 | case "dependencies": 14 | diff --git a/dist/changesets-assemble-release-plan.esm.js b/dist/changesets-assemble-release-plan.esm.js 15 | index f6583cf3f639e1fe4df764a015689dea74127236..2b9dca9f460793596394484457a94a34bcc1d99a 100644 16 | --- a/dist/changesets-assemble-release-plan.esm.js 17 | +++ b/dist/changesets-assemble-release-plan.esm.js 18 | @@ -178,7 +178,7 @@ function determineDependents({ 19 | preInfo, 20 | onlyUpdatePeerDependentsWhenOutOfRange: config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH.onlyUpdatePeerDependentsWhenOutOfRange 21 | })) { 22 | - type = "major"; 23 | + type = "minor"; 24 | } else if ((!releases.has(dependent) || releases.get(dependent).type === "none") && (config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH.updateInternalDependents === "always" || !semverSatisfies(incrementVersion(nextRelease, preInfo), versionRange))) { 25 | switch (depType) { 26 | case "dependencies": 27 | -------------------------------------------------------------------------------- /public/codemods/effect-3.0.4.ts: -------------------------------------------------------------------------------- 1 | import type cs from "jscodeshift" 2 | 3 | export default function transformer(file: cs.FileInfo, api: cs.API) { 4 | const j = api.jscodeshift 5 | const root = j(file.source) 6 | 7 | root.find(j.CallExpression).forEach(call => { 8 | if ( 9 | call.node.callee.type === "MemberExpression" 10 | && call.node.callee.property.type === "Identifier" 11 | && call.node.callee.property.name === "gen" 12 | ) { 13 | if (call.node.arguments.length > 0 && call.node.arguments.length <= 2) { 14 | const arg = call.node.arguments[call.node.arguments.length - 1] 15 | if ( 16 | arg.type === "FunctionExpression" && arg.generator === true 17 | && arg.params.length === 1 && arg.params[0].type === "Identifier" 18 | ) { 19 | const adapter = arg.params[0].name 20 | arg.params = [] 21 | j(arg.body).find(j.YieldExpression).forEach(yieldExpr => { 22 | if (yieldExpr.node.argument?.type === "CallExpression") { 23 | const call = yieldExpr.node.argument 24 | if ( 25 | call.callee.type === "Identifier" 26 | && call.callee.name === adapter 27 | ) { 28 | if ( 29 | call.arguments.length === 1 30 | && call.arguments[0].type !== "SpreadElement" 31 | ) { 32 | yieldExpr.node.argument = call.arguments[0] 33 | } else if ( 34 | call.arguments.length > 1 35 | && call.arguments[0].type !== "SpreadElement" 36 | ) { 37 | yieldExpr.node.argument = j.callExpression( 38 | j.memberExpression( 39 | call.arguments[0], 40 | j.identifier("pipe"), 41 | ), 42 | call.arguments.slice(1), 43 | ) 44 | } 45 | } 46 | } 47 | }) 48 | } 49 | } 50 | } 51 | }) 52 | 53 | return root.toSource() 54 | } 55 | -------------------------------------------------------------------------------- /patches/@changesets__get-github-info@0.6.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/dist/changesets-get-github-info.cjs.js b/dist/changesets-get-github-info.cjs.js 2 | index a74df59f8a5988f458a3476087399f5e6dfe4818..ce5e60ef9916eb0cb76ab1e9dd422abcad752bf6 100644 3 | --- a/dist/changesets-get-github-info.cjs.js 4 | +++ b/dist/changesets-get-github-info.cjs.js 5 | @@ -251,18 +251,13 @@ async function getInfo(request) { 6 | b = new Date(b.mergedAt); 7 | return a > b ? 1 : a < b ? -1 : 0; 8 | })[0] : null; 9 | - 10 | - if (associatedPullRequest) { 11 | - user = associatedPullRequest.author; 12 | - } 13 | - 14 | return { 15 | user: user ? user.login : null, 16 | pull: associatedPullRequest ? associatedPullRequest.number : null, 17 | links: { 18 | commit: `[\`${request.commit.slice(0, 7)}\`](${data.commitUrl})`, 19 | pull: associatedPullRequest ? `[#${associatedPullRequest.number}](${associatedPullRequest.url})` : null, 20 | - user: user ? `[@${user.login}](${user.url})` : null 21 | + user: user ? `@${user.login}` : null 22 | } 23 | }; 24 | } 25 | diff --git a/dist/changesets-get-github-info.esm.js b/dist/changesets-get-github-info.esm.js 26 | index 27e5c972ab1202ff16f5124b471f4bbcc46be2b5..3940a8fe86e10cb46d8ff6436dea1103b1839927 100644 27 | --- a/dist/changesets-get-github-info.esm.js 28 | +++ b/dist/changesets-get-github-info.esm.js 29 | @@ -242,18 +242,13 @@ async function getInfo(request) { 30 | b = new Date(b.mergedAt); 31 | return a > b ? 1 : a < b ? -1 : 0; 32 | })[0] : null; 33 | - 34 | - if (associatedPullRequest) { 35 | - user = associatedPullRequest.author; 36 | - } 37 | - 38 | return { 39 | user: user ? user.login : null, 40 | pull: associatedPullRequest ? associatedPullRequest.number : null, 41 | links: { 42 | commit: `[\`${request.commit.slice(0, 7)}\`](${data.commitUrl})`, 43 | pull: associatedPullRequest ? `[#${associatedPullRequest.number}](${associatedPullRequest.url})` : null, 44 | - user: user ? `[@${user.login}](${user.url})` : null 45 | + user: user ? `@${user.login}` : null 46 | } 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /public/codemods/minor-2.1.ts: -------------------------------------------------------------------------------- 1 | import type cs from "jscodeshift" 2 | import type { Collection } from "jscodeshift/src/Collection" 3 | 4 | export default function transformer(file: cs.FileInfo, api: cs.API) { 5 | const j = api.jscodeshift 6 | const root = j(file.source) 7 | 8 | forEveryTypeReference(root, j, ast => { 9 | patchSchema(ast, j) 10 | }) 11 | 12 | return root.toSource() 13 | } 14 | 15 | // 16 | // utilities 17 | // 18 | 19 | const patchSchema = ( 20 | ast: cs.ASTPath, 21 | j: cs.API["jscodeshift"], 22 | ) => { 23 | if (hasName(ast, "Schema") && ast.value.typeParameters?.params.length === 2) { 24 | const params = ast.value.typeParameters.params 25 | ast.value.typeParameters.params = [j.tsNeverKeyword(), ...params] 26 | } 27 | } 28 | 29 | const hasName = (reference: cs.ASTPath, name: string) => { 30 | const initial = reference.value.typeName 31 | const loop = (node: typeof initial): boolean => { 32 | switch (node.type) { 33 | case "Identifier": { 34 | return node.name === name 35 | } 36 | case "JSXIdentifier": { 37 | return false 38 | } 39 | case "TSQualifiedName": { 40 | return loop(node.right) 41 | } 42 | case "TSTypeParameter": { 43 | return false 44 | } 45 | } 46 | } 47 | return loop(initial) 48 | } 49 | 50 | // 51 | // this is needed to resolve a bug in jscodeshift that 52 | // forgets to traverse type parameters in call expressions 53 | // 54 | 55 | declare module "ast-types/gen/namedTypes" { 56 | namespace namedTypes { 57 | interface CallExpression extends TSHasOptionalTypeParameterInstantiation {} 58 | } 59 | } 60 | 61 | const forEveryTypeReference = ( 62 | node: Collection, 63 | j: cs.API["jscodeshift"], 64 | f: (ast: cs.ASTPath) => void, 65 | ) => { 66 | const visited = new Set() 67 | node.find(j.TSTypeReference).forEach(ast => { 68 | if (!visited.has(ast)) { 69 | visited.add(ast) 70 | f(ast) 71 | } 72 | }) 73 | node.find(j.CallExpression).forEach(path => { 74 | const typeParams = path.value.typeParameters 75 | if (typeParams) { 76 | j(typeParams).find(j.TSTypeReference).forEach(ast => { 77 | if (!visited.has(ast)) { 78 | visited.add(ast) 79 | f(ast) 80 | } 81 | }) 82 | } 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@effect/codemod", 3 | "version": "0.0.16", 4 | "publishConfig": { 5 | "access": "public", 6 | "directory": "dist" 7 | }, 8 | "packageManager": "pnpm@9.0.4", 9 | "description": "Code mod's for the Effect ecosystem", 10 | "engines": { 11 | "node": ">=20.12.2" 12 | }, 13 | "scripts": { 14 | "lint": "eslint . --ext .ts,.tsx", 15 | "autofix": "pnpm lint --fix", 16 | "coverage": "vitest run --coverage", 17 | "test": "vitest run", 18 | "clean": "rimraf dist/*", 19 | "check": "tsc -b tsconfig.json", 20 | "build": "tsup && pnpm copy-package-json", 21 | "build:ts": "tsup", 22 | "copy-package-json": "ts-node scripts/copy-package-json.ts", 23 | "changeset-version": "changeset version", 24 | "changeset-publish": "pnpm build && TEST_DIST= pnpm vitest && changeset publish" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/effect-ts/codemod.git" 29 | }, 30 | "author": "Tim Smart ", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/effect-ts/codemod/issues" 34 | }, 35 | "homepage": "https://github.com/effect-ts/codemod", 36 | "dependencies": { 37 | "jscodeshift": "^0.16.1" 38 | }, 39 | "devDependencies": { 40 | "@changesets/changelog-github": "^0.5.0", 41 | "@changesets/cli": "^2.27.7", 42 | "@effect/cli": "0.38.2", 43 | "@effect/eslint-plugin": "^0.1.2", 44 | "@effect/language-service": "^0.1.0", 45 | "@effect/platform": "^0.59.2", 46 | "@effect/platform-node": "^0.54.3", 47 | "@effect/schema": "^0.68.26", 48 | "@types/doctrine": "0.0.9", 49 | "@types/fs-extra": "^11.0.4", 50 | "@types/glob": "^8.1.0", 51 | "@types/jscodeshift": "^0.11.11", 52 | "@types/node": "^20.14.11", 53 | "@types/prettier": "^3.0.0", 54 | "@typescript-eslint/eslint-plugin": "^7.16.1", 55 | "@typescript-eslint/parser": "^7.16.1", 56 | "@vitest/coverage-v8": "^2.0.3", 57 | "ast-types": "^0.14.2", 58 | "effect": "3.5.6", 59 | "eslint": "^8.57.0", 60 | "eslint-import-resolver-typescript": "^3.6.1", 61 | "eslint-plugin-codegen": "0.28.0", 62 | "eslint-plugin-deprecation": "^3.0.0", 63 | "eslint-plugin-import": "^2.29.1", 64 | "eslint-plugin-simple-import-sort": "^12.1.1", 65 | "eslint-plugin-sort-destructure-keys": "^2.0.0", 66 | "glob": "^11.0.0", 67 | "prettier": "^3.3.3", 68 | "rimraf": "^6.0.1", 69 | "ts-node": "^10.9.2", 70 | "tsup": "^8.1.2", 71 | "typescript": "^5.5.3", 72 | "vite": "^5.3.4", 73 | "vitest": "^2.0.3" 74 | }, 75 | "pnpm": { 76 | "patchedDependencies": { 77 | "@changesets/get-github-info@0.6.0": "patches/@changesets__get-github-info@0.6.0.patch", 78 | "@changesets/assemble-release-plan@6.0.3": "patches/@changesets__assemble-release-plan@6.0.3.patch" 79 | }, 80 | "updateConfig": { 81 | "ignoreDependencies": [ 82 | "eslint" 83 | ] 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | module.exports = { 3 | ignorePatterns: ["build", "dist", "dtslint", "*.mjs", "docs", "*.md"], 4 | parser: "@typescript-eslint/parser", 5 | parserOptions: { 6 | ecmaVersion: 2018, 7 | sourceType: "module", 8 | project: "./tsconfig.eslint.json", 9 | }, 10 | settings: { 11 | "import/parsers": { 12 | "@typescript-eslint/parser": [".ts", ".tsx"], 13 | }, 14 | "import/resolver": { 15 | typescript: { 16 | alwaysTryTypes: true, 17 | }, 18 | }, 19 | }, 20 | extends: [ 21 | "eslint:recommended", 22 | "plugin:@typescript-eslint/eslint-recommended", 23 | "plugin:@typescript-eslint/recommended", 24 | "plugin:@effect/eslint-plugin/recommended", 25 | ], 26 | plugins: [ 27 | "deprecation", 28 | "import", 29 | "sort-destructure-keys", 30 | "simple-import-sort", 31 | "codegen", 32 | ], 33 | rules: { 34 | "codegen/codegen": "error", 35 | "no-fallthrough": "off", 36 | "no-irregular-whitespace": "off", 37 | "object-shorthand": "error", 38 | "prefer-destructuring": "off", 39 | "sort-imports": "off", 40 | "no-unused-vars": "off", 41 | "prefer-rest-params": "off", 42 | "prefer-spread": "off", 43 | "import/first": "error", 44 | "import/no-cycle": "error", 45 | "import/newline-after-import": "error", 46 | "import/no-duplicates": "error", 47 | "import/no-unresolved": "off", 48 | "import/order": "off", 49 | "simple-import-sort/imports": "off", 50 | "sort-destructure-keys/sort-destructure-keys": "error", 51 | "deprecation/deprecation": "off", 52 | "@typescript-eslint/array-type": [ 53 | "warn", 54 | { default: "generic", readonly: "generic" }, 55 | ], 56 | "@typescript-eslint/prefer-readonly": "warn", 57 | "@typescript-eslint/member-delimiter-style": 0, 58 | "@typescript-eslint/no-non-null-assertion": "off", 59 | "@typescript-eslint/ban-types": "off", 60 | "@typescript-eslint/no-explicit-any": "off", 61 | "@typescript-eslint/no-empty-interface": "off", 62 | "@typescript-eslint/consistent-type-imports": "warn", 63 | "@typescript-eslint/no-unused-vars": [ 64 | "error", 65 | { 66 | argsIgnorePattern: "^_", 67 | varsIgnorePattern: "^_", 68 | }, 69 | ], 70 | "@typescript-eslint/ban-ts-comment": "off", 71 | "@typescript-eslint/camelcase": "off", 72 | "@typescript-eslint/explicit-function-return-type": "off", 73 | "@typescript-eslint/explicit-module-boundary-types": "off", 74 | "@typescript-eslint/interface-name-prefix": "off", 75 | "@typescript-eslint/no-array-constructor": "off", 76 | "@typescript-eslint/no-use-before-define": "off", 77 | "@typescript-eslint/no-namespace": "off", 78 | "@effect/dprint": [ 79 | "error", 80 | { 81 | config: { 82 | indentWidth: 2, 83 | lineWidth: 80, 84 | semiColons: "asi", 85 | quoteStyle: "alwaysDouble", 86 | "arrowFunction.useParentheses": "preferNone", 87 | }, 88 | }, 89 | ], 90 | }, 91 | } 92 | -------------------------------------------------------------------------------- /public/codemods/platform-0.49.ts: -------------------------------------------------------------------------------- 1 | import type k from "ast-types/gen/kinds.js" 2 | import type cs from "jscodeshift" 3 | import type { Collection } from "jscodeshift/src/Collection" 4 | 5 | export default function transformer(file: cs.FileInfo, api: cs.API) { 6 | const j = api.jscodeshift 7 | const root = j(file.source) 8 | 9 | forEveryTypeReference(root, j, ast => { 10 | swapParams(ast, "Client", 3) 11 | swapParams(ast, "WithResponse", 3) 12 | swapParams(ast, "HttpApp", 3) 13 | swapParams(ast, "Multiplex", 2, { popSize: 2 }) 14 | swapParams(ast, "Router", 2, { popSize: 2 }) 15 | swapParams(ast, "Route", 2, { popSize: 2 }) 16 | }) 17 | 18 | return root.toSource() 19 | } 20 | 21 | // 22 | // utilities 23 | // 24 | 25 | const swapParams = ( 26 | ast: cs.ASTPath, 27 | name: string, 28 | size: number, 29 | options?: { 30 | readonly popSize?: number 31 | }, 32 | ) => { 33 | if (hasName(ast, name) && ast.value.typeParameters?.params.length === size) { 34 | const params = ast.value.typeParameters.params 35 | params.reverse() 36 | const popSize = options?.popSize ?? size - 1 37 | for (let i = 0; i < popSize; i++) { 38 | popNever(params) 39 | } 40 | } 41 | } 42 | 43 | const popNever = (params: Array) => { 44 | if ( 45 | params.length > 0 46 | && params[params.length - 1].type === "TSNeverKeyword" 47 | ) { 48 | params.pop() 49 | } 50 | } 51 | 52 | const hasName = (reference: cs.ASTPath, name: string) => { 53 | const initial = reference.value.typeName 54 | const loop = (node: typeof initial): boolean => { 55 | switch (node.type) { 56 | case "Identifier": { 57 | return node.name === name 58 | } 59 | case "JSXIdentifier": { 60 | return false 61 | } 62 | case "TSQualifiedName": { 63 | return loop(node.right) 64 | } 65 | case "TSTypeParameter": { 66 | return false 67 | } 68 | } 69 | } 70 | return loop(initial) 71 | } 72 | 73 | // 74 | // this is needed to resolve a bug in jscodeshift that 75 | // forgets to traverse type parameters in call expressions 76 | // 77 | 78 | declare module "ast-types/gen/namedTypes" { 79 | namespace namedTypes { 80 | interface CallExpression extends TSHasOptionalTypeParameterInstantiation {} 81 | } 82 | } 83 | 84 | const forEveryTypeReference = ( 85 | node: Collection, 86 | j: cs.API["jscodeshift"], 87 | f: (ast: cs.ASTPath) => void, 88 | ) => { 89 | const visited = new Set() 90 | node.find(j.TSTypeReference).forEach(ast => { 91 | if (!visited.has(ast)) { 92 | visited.add(ast) 93 | f(ast) 94 | } 95 | }) 96 | node.find(j.CallExpression).forEach(path => { 97 | const typeParams = path.value.typeParameters 98 | if (typeParams) { 99 | j(typeParams).find(j.TSTypeReference).forEach(ast => { 100 | if (!visited.has(ast)) { 101 | visited.add(ast) 102 | f(ast) 103 | } 104 | }) 105 | } 106 | }) 107 | } 108 | -------------------------------------------------------------------------------- /public/codemods/minor-2.4.ts: -------------------------------------------------------------------------------- 1 | import type k from "ast-types/gen/kinds.js" 2 | import type cs from "jscodeshift" 3 | import type { Collection } from "jscodeshift/src/Collection" 4 | 5 | export default function transformer(file: cs.FileInfo, api: cs.API) { 6 | const j = api.jscodeshift 7 | const root = j(file.source) 8 | 9 | forEveryTypeReference(root, j, ast => { 10 | swapParams(ast, "Either", 2) 11 | swapSchedule(ast) 12 | }) 13 | 14 | return root.toSource() 15 | } 16 | 17 | // 18 | // utilities 19 | // 20 | 21 | const swapSchedule = ( 22 | ast: cs.ASTPath, 23 | ) => { 24 | if ( 25 | hasName(ast, "Schedule") && ast.value.typeParameters?.params.length === 3 26 | ) { 27 | const params = ast.value.typeParameters.params 28 | params.reverse() 29 | if (params[2].type === "TSNeverKeyword") { 30 | popNever(params) 31 | if (params[1].type === "TSUnknownKeyword") { 32 | popUnknown(params) 33 | } 34 | } 35 | } 36 | } 37 | 38 | const swapParams = ( 39 | ast: cs.ASTPath, 40 | name: string, 41 | size: number, 42 | ) => { 43 | if (hasName(ast, name) && ast.value.typeParameters?.params.length === size) { 44 | const params = ast.value.typeParameters.params 45 | params.reverse() 46 | for (let i = 0; i < size - 1; i++) { 47 | popNever(params) 48 | } 49 | } 50 | } 51 | 52 | const popNever = (params: Array) => { 53 | if ( 54 | params.length > 0 55 | && params[params.length - 1].type === "TSNeverKeyword" 56 | ) { 57 | params.pop() 58 | } 59 | } 60 | 61 | const popUnknown = (params: Array) => { 62 | if ( 63 | params.length > 0 64 | && params[params.length - 1].type === "TSUnknownKeyword" 65 | ) { 66 | params.pop() 67 | } 68 | } 69 | 70 | const hasName = (reference: cs.ASTPath, name: string) => { 71 | const initial = reference.value.typeName 72 | const loop = (node: typeof initial): boolean => { 73 | switch (node.type) { 74 | case "Identifier": { 75 | return node.name === name 76 | } 77 | case "JSXIdentifier": { 78 | return false 79 | } 80 | case "TSQualifiedName": { 81 | return loop(node.right) 82 | } 83 | case "TSTypeParameter": { 84 | return false 85 | } 86 | } 87 | } 88 | return loop(initial) 89 | } 90 | 91 | // 92 | // this is needed to resolve a bug in jscodeshift that 93 | // forgets to traverse type parameters in call expressions 94 | // 95 | 96 | declare module "ast-types/gen/namedTypes" { 97 | namespace namedTypes { 98 | interface CallExpression extends TSHasOptionalTypeParameterInstantiation {} 99 | } 100 | } 101 | 102 | const forEveryTypeReference = ( 103 | node: Collection, 104 | j: cs.API["jscodeshift"], 105 | f: (ast: cs.ASTPath) => void, 106 | ) => { 107 | const visited = new Set() 108 | node.find(j.TSTypeReference).forEach(ast => { 109 | if (!visited.has(ast)) { 110 | visited.add(ast) 111 | f(ast) 112 | } 113 | }) 114 | node.find(j.CallExpression).forEach(path => { 115 | const typeParams = path.value.typeParameters 116 | if (typeParams) { 117 | j(typeParams).find(j.TSTypeReference).forEach(ast => { 118 | if (!visited.has(ast)) { 119 | visited.add(ast) 120 | f(ast) 121 | } 122 | }) 123 | } 124 | }) 125 | } 126 | -------------------------------------------------------------------------------- /test/effect-3.0.test.ts: -------------------------------------------------------------------------------- 1 | import { describe } from "vitest" 2 | import transformer from "../public/codemods/effect-3.0" 3 | import * as Utils from "./Utils" 4 | 5 | const expectTransformation = Utils.expectTransformation(transformer) 6 | 7 | describe("ReadonlyArray -> Array", () => { 8 | expectTransformation( 9 | `import * as ReadonlyArray from "effect/ReadonlyArray`, 10 | `import * as ReadonlyArray from "effect/ReadonlyArray" 11 | import * as Option from "effect/Option"`, 12 | `import * as ReadonlyArray from "effect/Array" 13 | import * as Option from "effect/Option"`, 14 | ) 15 | 16 | expectTransformation( 17 | `import type * as ReadonlyArray from "effect/ReadonlyArray`, 18 | `import type * as ReadonlyArray from "effect/ReadonlyArray" 19 | import * as Option from "effect/Option"`, 20 | `import type * as ReadonlyArray from "effect/Array" 21 | import * as Option from "effect/Option"`, 22 | ) 23 | 24 | expectTransformation( 25 | `import { range } from "effect/ReadonlyArray`, 26 | `import { range } from "effect/ReadonlyArray" 27 | import * as Option from "effect/Option"`, 28 | `import { range } from "effect/Array" 29 | import * as Option from "effect/Option"`, 30 | ) 31 | 32 | expectTransformation( 33 | `import type { NonEmptyReadonlyArray } from "effect/ReadonlyArray`, 34 | `import type { NonEmptyReadonlyArray } from "effect/ReadonlyArray" 35 | import * as Option from "effect/Option"`, 36 | `import type { NonEmptyReadonlyArray } from "effect/Array" 37 | import * as Option from "effect/Option"`, 38 | ) 39 | 40 | expectTransformation( 41 | `import { ReadonlyArray, Option } from "effect`, 42 | `import { ReadonlyArray, Option } from "effect"`, 43 | `import { Array as ReadonlyArray, Option } from "effect"`, 44 | ) 45 | 46 | expectTransformation( 47 | `import type { ReadonlyArray, Option } from "effect`, 48 | `import type { ReadonlyArray, Option } from "effect"`, 49 | `import type { Array as ReadonlyArray, Option } from "effect"`, 50 | ) 51 | 52 | expectTransformation( 53 | `import { ReadonlyArray as RA, Option } from "effect`, 54 | `import { ReadonlyArray as RA, Option } from "effect"`, 55 | `import { Array as RA, Option } from "effect"`, 56 | ) 57 | }) 58 | 59 | describe("ReadonlyRecord -> Record", () => { 60 | expectTransformation( 61 | `import * as ReadonlyRecord from "effect/ReadonlyRecord`, 62 | `import * as ReadonlyRecord from "effect/ReadonlyRecord" 63 | import * as Option from "effect/Option"`, 64 | `import * as ReadonlyRecord from "effect/Record" 65 | import * as Option from "effect/Option"`, 66 | ) 67 | 68 | expectTransformation( 69 | `import type * as ReadonlyRecord from "effect/ReadonlyRecord`, 70 | `import type * as ReadonlyRecord from "effect/ReadonlyRecord" 71 | import * as Option from "effect/Option"`, 72 | `import type * as ReadonlyRecord from "effect/Record" 73 | import * as Option from "effect/Option"`, 74 | ) 75 | 76 | expectTransformation( 77 | `import { empty } from "effect/ReadonlyRecord`, 78 | `import { empty } from "effect/ReadonlyRecord" 79 | import * as Option from "effect/Option"`, 80 | `import { empty } from "effect/Record" 81 | import * as Option from "effect/Option"`, 82 | ) 83 | 84 | expectTransformation( 85 | `import type { ReadonlyRecord } from "effect/ReadonlyRecord`, 86 | `import type { ReadonlyRecord } from "effect/ReadonlyRecord" 87 | import * as Option from "effect/Option"`, 88 | `import type { ReadonlyRecord } from "effect/Record" 89 | import * as Option from "effect/Option"`, 90 | ) 91 | 92 | expectTransformation( 93 | `import { ReadonlyRecord Option } from "effect`, 94 | `import { ReadonlyRecord, Option } from "effect"`, 95 | `import { Record as ReadonlyRecord, Option } from "effect"`, 96 | ) 97 | 98 | expectTransformation( 99 | `import type { ReadonlyRecord, Option } from "effect`, 100 | `import type { ReadonlyRecord, Option } from "effect"`, 101 | `import type { Record as ReadonlyRecord, Option } from "effect"`, 102 | ) 103 | 104 | expectTransformation( 105 | `import type { ReadonlyRecord as RR, Option } from "effect`, 106 | `import type { ReadonlyRecord as RR, Option } from "effect"`, 107 | `import type { Record as RR, Option } from "effect"`, 108 | ) 109 | 110 | expectTransformation( 111 | "unit -> void", 112 | `Effect.unit 113 | Effect.asUnit 114 | Exit.unit 115 | Option.unit 116 | Stream.unit`, 117 | `Effect.void 118 | Effect.asVoid 119 | Exit.void 120 | Option.void 121 | Stream.void`, 122 | ) 123 | }) 124 | -------------------------------------------------------------------------------- /public/Utils.ts: -------------------------------------------------------------------------------- 1 | import type { ExpressionKind } from "ast-types/gen/kinds" 2 | import type cs from "jscodeshift" 3 | import type { Collection } from "jscodeshift/src/Collection" 4 | 5 | export const orElse = ( 6 | x: A | undefined, 7 | f: () => A | undefined, 8 | ): A | undefined => x === undefined ? f() : x 9 | 10 | /** 11 | * - given `import * as Namespace from "source"` returns "Namespace" 12 | * - given `import type * as Namespace from "source"` returns "Namespace" 13 | */ 14 | export const getNamespaceImport = ( 15 | file: cs.FileInfo, 16 | api: cs.API, 17 | source: string, 18 | type: boolean, 19 | ): string | undefined => { 20 | const j = api.jscodeshift 21 | const importDeclarations = findImportDeclarations(file, api, source, type) 22 | if (importDeclarations.length > 0) { 23 | const name = importDeclarations.find(j.Identifier).get(0).node.name 24 | if (typeof name === "string") { 25 | return name 26 | } 27 | } 28 | return undefined 29 | } 30 | 31 | const findImportDeclarations = ( 32 | file: cs.FileInfo, 33 | api: cs.API, 34 | source: string, 35 | type: boolean, 36 | ) => { 37 | const j = api.jscodeshift 38 | return j(file.source).find(j.ImportDeclaration, { 39 | source: { value: source }, 40 | }).filter(path => 41 | type ? path.value.importKind === "type" : path.value.importKind === "value" 42 | ) 43 | } 44 | 45 | /** 46 | * - given `import { Named } from "source"` returns "Named" 47 | * - given `import { Named as N } from "source"` returns "N" 48 | * - given `import type { Named } from "source"` returns "Named" 49 | * - given `import type { Named as N } from "source"` returns "N" 50 | */ 51 | export const getNamedImport = ( 52 | file: cs.FileInfo, 53 | api: cs.API, 54 | source: string, 55 | name: string, 56 | type: boolean, 57 | ): string | undefined => { 58 | const importDeclarations = findImportDeclarations(file, api, source, type) 59 | let out: string | undefined = undefined 60 | importDeclarations.forEach(path => { 61 | const specifiers = path.value.specifiers 62 | if (specifiers) { 63 | for (const specifier of specifiers) { 64 | if (specifier.type === "ImportSpecifier") { 65 | if (specifier.imported.name === name) { 66 | if (specifier.local) { 67 | out = specifier.local.name 68 | } else { 69 | out = name 70 | } 71 | break 72 | } 73 | } 74 | } 75 | } 76 | }) 77 | return out 78 | } 79 | 80 | /** 81 | * - replaces `import * as Name from "` with `import * as Name from "` 82 | * - replaces `import { Name } from "` with `import { Name } from "` 83 | */ 84 | export const replaceImportSource = ( 85 | api: cs.API, 86 | ast: Collection, 87 | fromSource: string, 88 | toSource: string, 89 | ) => { 90 | const j = api.jscodeshift 91 | ast.find(j.ImportDeclaration).forEach(path => { 92 | if (path.node.source.value === fromSource) { 93 | path.node.source = j.literal(toSource) 94 | } 95 | }) 96 | } 97 | 98 | /** 99 | * - replaces `import { } from ""` with `import { as } from ""` 100 | * - replaces `import type { } from ""` with `import type { as } from ""` 101 | */ 102 | export const replaceNamedImport = ( 103 | api: cs.API, 104 | ast: Collection, 105 | source: string, 106 | from: string, 107 | to: string, 108 | ) => { 109 | const j = api.jscodeshift 110 | ast.find(j.ImportDeclaration, { source: { value: source } }).forEach(path => { 111 | path.node.specifiers?.forEach(s => { 112 | if (s.type === "ImportSpecifier") { 113 | if (s.imported.name === from) { 114 | s.imported.name = to 115 | s.name = j.identifier(from) 116 | } 117 | } 118 | }) 119 | }) 120 | } 121 | 122 | export const renameMember = (ast: ExpressionKind, name: string): void => { 123 | switch (ast.type) { 124 | case "Identifier": { 125 | ast.name = name 126 | return 127 | } 128 | case "MemberExpression": { 129 | return renameMember(ast.property, name) 130 | } 131 | default: { 132 | return 133 | } 134 | } 135 | } 136 | 137 | export const renameMembers = ( 138 | api: cs.API, 139 | ast: Collection, 140 | object: string, 141 | fromProp: string, 142 | toProp: string, 143 | ) => { 144 | const j = api.jscodeshift 145 | ast.find(j.MemberExpression).filter(path => 146 | path.node.object.type === "Identifier" && path.node.object.name === object 147 | ).filter(path => 148 | path.node.property.type === "Identifier" 149 | && path.node.property.name === fromProp 150 | ).forEach(path => { 151 | renameMember(path.value, toProp) 152 | }) 153 | } 154 | -------------------------------------------------------------------------------- /test/Utils.test.ts: -------------------------------------------------------------------------------- 1 | import type cs from "jscodeshift" 2 | import type { Collection } from "jscodeshift/src/Collection" 3 | import { describe } from "vitest" 4 | import * as Utils from "../public/Utils" 5 | import { expectTransformation } from "./Utils" 6 | 7 | const addOutput = (api: cs.API, ast: Collection, value: string) => { 8 | const j = api.jscodeshift 9 | const newConst = j.variableDeclaration("const", [ 10 | j.variableDeclarator(j.identifier("output"), j.stringLiteral(value)), 11 | ]) 12 | ast.find(j.Program).get("body", 0).insertAfter(newConst) 13 | } 14 | 15 | describe("getNamespaceImport", () => { 16 | const T = (type: boolean) => (file: cs.FileInfo, api: cs.API) => { 17 | const j = api.jscodeshift 18 | const ast = j(file.source) 19 | 20 | addOutput( 21 | api, 22 | ast, 23 | Utils.getNamespaceImport(file, api, "source", type) ?? "NOT_FOUND", 24 | ) 25 | 26 | return ast.toSource() 27 | } 28 | 29 | expectTransformation(T(false))( 30 | `import * as Namespace from "xxx" (type = false)`, 31 | `import * as Namespace from "xxx"`, 32 | `import * as Namespace from "xxx" 33 | const output = "NOT_FOUND";`, 34 | ) 35 | 36 | expectTransformation(T(true))( 37 | `import type * as Namespace from "xxx" (type = true)`, 38 | `import type * as Namespace from "xxx"`, 39 | `import type * as Namespace from "xxx" 40 | const output = "NOT_FOUND";`, 41 | ) 42 | 43 | expectTransformation(T(false))( 44 | `import * as Namespace from "source" (type = false)`, 45 | `import * as Namespace from "source"`, 46 | `import * as Namespace from "source" 47 | const output = "Namespace";`, 48 | ) 49 | 50 | expectTransformation(T(true))( 51 | `import * as Namespace from "source" (type = true)`, 52 | `import * as Namespace from "source"`, 53 | `import * as Namespace from "source" 54 | const output = "NOT_FOUND";`, 55 | ) 56 | 57 | expectTransformation(T(false))( 58 | `import type * as Namespace from "source" (type = false)`, 59 | `import type * as Namespace from "source"`, 60 | `import type * as Namespace from "source" 61 | const output = "NOT_FOUND";`, 62 | ) 63 | 64 | expectTransformation(T(true))( 65 | `import type * as Namespace from "source" (type = true)`, 66 | `import type * as Namespace from "source"`, 67 | `import type * as Namespace from "source" 68 | const output = "Namespace";`, 69 | ) 70 | }) 71 | 72 | describe("getNamedImport", () => { 73 | const T = (type: boolean) => (file: cs.FileInfo, api: cs.API) => { 74 | const j = api.jscodeshift 75 | const ast = j(file.source) 76 | 77 | addOutput( 78 | api, 79 | ast, 80 | Utils.getNamedImport(file, api, "source", "Named", type) ?? "NOT_FOUND", 81 | ) 82 | 83 | return ast.toSource() 84 | } 85 | 86 | expectTransformation(T(false))( 87 | `import { Named } from "source" (type = false)`, 88 | `import { Named } from "source"`, 89 | `import { Named } from "source" 90 | const output = "Named";`, 91 | ) 92 | 93 | expectTransformation(T(false))( 94 | `import { Named as N } from "source" (type = false)`, 95 | `import { Named as N } from "source"`, 96 | `import { Named as N } from "source" 97 | const output = "N";`, 98 | ) 99 | 100 | expectTransformation(T(false))( 101 | `import type { Named } from "source" (type = false)`, 102 | `import type { Named } from "source"`, 103 | `import type { Named } from "source" 104 | const output = "NOT_FOUND";`, 105 | ) 106 | 107 | expectTransformation(T(false))( 108 | `import type { Named as N } from "source" (type = false)`, 109 | `import type { Named as N } from "source"`, 110 | `import type { Named as N } from "source" 111 | const output = "NOT_FOUND";`, 112 | ) 113 | 114 | expectTransformation(T(true))( 115 | `import { Named } from "source" (type = true)`, 116 | `import { Named } from "source"`, 117 | `import { Named } from "source" 118 | const output = "NOT_FOUND";`, 119 | ) 120 | 121 | expectTransformation(T(true))( 122 | `import { Named as N } from "source" (type = true)`, 123 | `import { Named as N } from "source"`, 124 | `import { Named as N } from "source" 125 | const output = "NOT_FOUND";`, 126 | ) 127 | 128 | expectTransformation(T(true))( 129 | `import type { Named } from "source" (type = true)`, 130 | `import type { Named } from "source"`, 131 | `import type { Named } from "source" 132 | const output = "Named";`, 133 | ) 134 | 135 | expectTransformation(T(true))( 136 | `import type { Named as N } from "source" (type = true)`, 137 | `import type { Named as N } from "source"`, 138 | `import type { Named as N } from "source" 139 | const output = "N";`, 140 | ) 141 | }) 142 | 143 | describe("replaceImportSource", () => { 144 | const T = (file: cs.FileInfo, api: cs.API) => { 145 | const j = api.jscodeshift 146 | const ast = j(file.source) 147 | Utils.replaceImportSource( 148 | api, 149 | ast, 150 | "effect/ReadonlyArray", 151 | "effect/Array", 152 | ) 153 | return ast.toSource() 154 | } 155 | 156 | expectTransformation(T)( 157 | `import * as ReadonlyArray from "effect/ReadonlyArray`, 158 | `import * as ReadonlyArray from "effect/ReadonlyArray" 159 | import * as Option from "effect/Option"`, 160 | `import * as ReadonlyArray from "effect/Array" 161 | import * as Option from "effect/Option"`, 162 | ) 163 | 164 | expectTransformation(T)( 165 | `import type * as ReadonlyArray from "effect/ReadonlyArray`, 166 | `import type * as ReadonlyArray from "effect/ReadonlyArray" 167 | import * as Option from "effect/Option"`, 168 | `import type * as ReadonlyArray from "effect/Array" 169 | import * as Option from "effect/Option"`, 170 | ) 171 | 172 | expectTransformation(T)( 173 | `import { range } from "effect/ReadonlyArray`, 174 | `import { range } from "effect/ReadonlyArray" 175 | import * as Option from "effect/Option"`, 176 | `import { range } from "effect/Array" 177 | import * as Option from "effect/Option"`, 178 | ) 179 | 180 | expectTransformation(T)( 181 | `import type { NonEmptyReadonlyArray } from "effect/ReadonlyArray`, 182 | `import type { NonEmptyReadonlyArray } from "effect/ReadonlyArray" 183 | import * as Option from "effect/Option"`, 184 | `import type { NonEmptyReadonlyArray } from "effect/Array" 185 | import * as Option from "effect/Option"`, 186 | ) 187 | }) 188 | 189 | describe("replaceNamedImport", () => { 190 | const T = (file: cs.FileInfo, api: cs.API) => { 191 | const j = api.jscodeshift 192 | const ast = j(file.source) 193 | Utils.replaceNamedImport( 194 | api, 195 | ast, 196 | "effect", 197 | "ReadonlyArray", 198 | "Array", 199 | ) 200 | return ast.toSource() 201 | } 202 | 203 | expectTransformation(T)( 204 | `import { ReadonlyArray, Option } from "effect`, 205 | `import { ReadonlyArray, Option } from "effect"`, 206 | `import { Array as ReadonlyArray, Option } from "effect"`, 207 | ) 208 | 209 | expectTransformation(T)( 210 | `import type { ReadonlyArray, Option } from "effect`, 211 | `import type { ReadonlyArray, Option } from "effect"`, 212 | `import type { Array as ReadonlyArray, Option } from "effect"`, 213 | ) 214 | }) 215 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @effect/codemod 2 | 3 | ## 0.0.16 4 | 5 | ### Patch Changes 6 | 7 | - [#39](https://github.com/Effect-TS/codemod/pull/39) [`183d054`](https://github.com/Effect-TS/codemod/commit/183d0541942406ad6d2c6959d2d7fb0ed1d05b47) Thanks @gcanti! - schema-0.69 codemod 8 | 9 | - [#40](https://github.com/Effect-TS/codemod/pull/40) [`97a4978`](https://github.com/Effect-TS/codemod/commit/97a4978786930dfbe938bf5e6f00928dbfd8d452) Thanks @IMax153! - update to latest jscodeshift 10 | 11 | ## 0.0.15 12 | 13 | ### Patch Changes 14 | 15 | - [#37](https://github.com/Effect-TS/codemod/pull/37) [`16a45d3`](https://github.com/Effect-TS/codemod/commit/16a45d303bbce1c659d7bb0cc8a91f09d8224f13) Thanks [@gcanti](https://github.com/gcanti)! - Fix: Update import from `formatError` to `formatErrorSync` in the schema 0.65 codemod, closes #36 16 | 17 | ## 0.0.14 18 | 19 | ### Patch Changes 20 | 21 | - [#34](https://github.com/Effect-TS/codemod/pull/34) [`bfc890c`](https://github.com/Effect-TS/codemod/commit/bfc890c7b45837c566dc8de482fe1b0806c290e0) Thanks [@mikearnaldi](https://github.com/mikearnaldi)! - Add effect-3.0.4 mod to remove gen adapter 22 | 23 | NOTE: some edge cases are uncovered like: 24 | 25 | ```ts 26 | yield * $([Effect.succeed(0), Effect.succeed(1)] as const, Effect.allWith()); 27 | ``` 28 | 29 | that needs to be convered to: 30 | 31 | ```ts 32 | yield * 33 | pipe([Effect.succeed(0), Effect.succeed(1)] as const, Effect.allWith()); 34 | ``` 35 | 36 | Unfortunately not having type information in the mod tool renders impossible to decide if the `pipe` function is present or not. 37 | 38 | ## 0.0.13 39 | 40 | ### Patch Changes 41 | 42 | - [#31](https://github.com/Effect-TS/codemod/pull/31) [`ce6e3c5`](https://github.com/Effect-TS/codemod/commit/ce6e3c51134bb110e31e85e0c0ec55f1ecda9115) Thanks [@tim-smart](https://github.com/tim-smart)! - remove use of "effect" in utils 43 | 44 | ## 0.0.12 45 | 46 | ### Patch Changes 47 | 48 | - [#29](https://github.com/Effect-TS/codemod/pull/29) [`d2577cc`](https://github.com/Effect-TS/codemod/commit/d2577cc799635aee444c717cb9bf70b0db3a104a) Thanks [@tim-smart](https://github.com/tim-smart)! - move Utils to public directory 49 | 50 | ## 0.0.11 51 | 52 | ### Patch Changes 53 | 54 | - [#27](https://github.com/Effect-TS/codemod/pull/27) [`712c2be`](https://github.com/Effect-TS/codemod/commit/712c2bef77d8d22419439c3dd5faa2b8f2214604) Thanks [@tim-smart](https://github.com/tim-smart)! - copy src to build output 55 | 56 | ## 0.0.10 57 | 58 | ### Patch Changes 59 | 60 | - [#25](https://github.com/Effect-TS/codemod/pull/25) [`740d77e`](https://github.com/Effect-TS/codemod/commit/740d77e05879f7ada1c2b84d1987ce05277ebac2) Thanks [@gcanti](https://github.com/gcanti)! - add codemod for effect major 3.0 61 | 62 | ## 0.0.9 63 | 64 | ### Patch Changes 65 | 66 | - [#23](https://github.com/Effect-TS/codemod/pull/23) [`88e32c0`](https://github.com/Effect-TS/codemod/commit/88e32c021af90815388ea32e00887e337fe2e5f2) Thanks [@gcanti](https://github.com/gcanti)! - update schema-0.65 codemod: 67 | 68 | - handle type-level Schema changes 69 | - handle LazyArbitrary type 70 | 71 | ## 0.0.8 72 | 73 | ### Patch Changes 74 | 75 | - [#21](https://github.com/Effect-TS/codemod/pull/21) [`2154c5b`](https://github.com/Effect-TS/codemod/commit/2154c5b08429ff8d675d30ccfc2f13527fc007e9) Thanks [@gcanti](https://github.com/gcanti)! - add codemod for schema 0.65 76 | 77 | ## 0.0.7 78 | 79 | ### Patch Changes 80 | 81 | - [#19](https://github.com/Effect-TS/codemod/pull/19) [`692f7f3`](https://github.com/Effect-TS/codemod/commit/692f7f3cdb21dc3353f2c667f22f48b62e072211) Thanks [@tim-smart](https://github.com/tim-smart)! - add codemod for platform 0.49 82 | 83 | ## 0.0.6 84 | 85 | ### Patch Changes 86 | 87 | - [#17](https://github.com/Effect-TS/codemod/pull/17) [`ed9cf84`](https://github.com/Effect-TS/codemod/commit/ed9cf84147073c12318bc1df5fbe06aa188e5158) Thanks [@mikearnaldi](https://github.com/mikearnaldi)! - Add codemod to swap Schedule params 88 | 89 | ## 0.0.5 90 | 91 | ### Patch Changes 92 | 93 | - [#14](https://github.com/Effect-TS/codemod/pull/14) [`e02e19a`](https://github.com/Effect-TS/codemod/commit/e02e19a99a46e5addd8a41a4aef17029f5e8d836) Thanks [@mikearnaldi](https://github.com/mikearnaldi)! - Add codemod for Either 94 | 95 | - [#16](https://github.com/Effect-TS/codemod/pull/16) [`66dc95e`](https://github.com/Effect-TS/codemod/commit/66dc95e149ceb4b2dd03887faa91288309f8c902) Thanks [@mikearnaldi](https://github.com/mikearnaldi)! - Add codemod for minor 2.1, adding R to Schema as Schema 96 | 97 | ## 0.0.4 98 | 99 | ### Patch Changes 100 | 101 | - [#12](https://github.com/Effect-TS/codemod/pull/12) [`ee757b7`](https://github.com/Effect-TS/codemod/commit/ee757b76b0dc878480c4cd4382513948a4ca78a4) Thanks [@tim-smart](https://github.com/tim-smart)! - add Pool to minor 2.3 102 | 103 | ## 0.0.3 104 | 105 | ### Patch Changes 106 | 107 | - [#8](https://github.com/Effect-TS/codemod/pull/8) [`e8af4eb`](https://github.com/Effect-TS/codemod/commit/e8af4eb0ced78d93ad9992c5c4e6c543f2886eff) Thanks [@mikearnaldi](https://github.com/mikearnaldi)! - Fuse codemods, include tag renaming 108 | 109 | - [#10](https://github.com/Effect-TS/codemod/pull/10) [`631d125`](https://github.com/Effect-TS/codemod/commit/631d12560f7f0786a1e23c32b669fcab590b7419) Thanks [@tim-smart](https://github.com/tim-smart)! - swap params for some common constructors 110 | 111 | - [#7](https://github.com/Effect-TS/codemod/pull/7) [`0e1556c`](https://github.com/Effect-TS/codemod/commit/0e1556c37755574099e1efe0905ee532dfa5ce9c) Thanks [@mikearnaldi](https://github.com/mikearnaldi)! - Add codemod to turn async() into async() considering never as default 112 | 113 | - [#11](https://github.com/Effect-TS/codemod/pull/11) [`3a42710`](https://github.com/Effect-TS/codemod/commit/3a42710f819f95382176d4ab4faf28e729f997f8) Thanks [@tim-smart](https://github.com/tim-smart)! - rename Tag imports to GenericTag 114 | 115 | - [#4](https://github.com/Effect-TS/codemod/pull/4) [`b40d225`](https://github.com/Effect-TS/codemod/commit/b40d225b20eb67c418d20749d1d2d2bde5e802a3) Thanks [@tim-smart](https://github.com/tim-smart)! - add descriptions for dry-run and print 116 | 117 | - [#9](https://github.com/Effect-TS/codemod/pull/9) [`66d50bb`](https://github.com/Effect-TS/codemod/commit/66d50bbc608501d91021b87425b3039f5fd19baf) Thanks [@tim-smart](https://github.com/tim-smart)! - add remaining data types to minor-2.3 118 | 119 | - [#6](https://github.com/Effect-TS/codemod/pull/6) [`f861455`](https://github.com/Effect-TS/codemod/commit/f86145583a3a864287ff840e570d4576470006ab) Thanks [@tim-smart](https://github.com/tim-smart)! - only use cli built-in error logging 120 | 121 | - [#3](https://github.com/Effect-TS/codemod/pull/3) [`0bc879b`](https://github.com/Effect-TS/codemod/commit/0bc879b8481b3d9dd5b9ace331a85aabb07cc02c) Thanks [@mikearnaldi](https://github.com/mikearnaldi)! - Add codemods for swap-type-params in Effect,Exit,STM,Stream,Layer,Schema and add-tag-identifier 122 | 123 | - [#9](https://github.com/Effect-TS/codemod/pull/9) [`66d50bb`](https://github.com/Effect-TS/codemod/commit/66d50bbc608501d91021b87425b3039f5fd19baf) Thanks [@tim-smart](https://github.com/tim-smart)! - update /cli 124 | 125 | ## 0.0.2 126 | 127 | ### Patch Changes 128 | 129 | - [`52b7ff8`](https://github.com/Effect-TS/codemod/commit/52b7ff8f90482068e4e9927d799583ea9d6c3e26) Thanks [@tim-smart](https://github.com/tim-smart)! - only log defects 130 | 131 | ## 0.0.1 132 | 133 | ### Patch Changes 134 | 135 | - [`23c7ad6`](https://github.com/Effect-TS/codemod/commit/23c7ad66dfcaa229596da0e585474ef42bc7b846) Thanks [@tim-smart](https://github.com/tim-smart)! - initial release 136 | -------------------------------------------------------------------------------- /public/codemods/minor-2.3.ts: -------------------------------------------------------------------------------- 1 | import type k from "ast-types/gen/kinds.js" 2 | import type cs from "jscodeshift" 3 | import type { Collection } from "jscodeshift/src/Collection" 4 | 5 | export default function transformer(file: cs.FileInfo, api: cs.API) { 6 | const j = api.jscodeshift 7 | const root = j(file.source) 8 | 9 | forEveryTypeReference(root, j, ast => { 10 | swapParams(ast, "Effect", 3) 11 | swapParams(ast, "Stream", 3) 12 | swapParams(ast, "STM", 3) 13 | swapParams(ast, "STMGen", 3) 14 | swapParams(ast, "Layer", 3) 15 | swapParams(ast, "Exit", 2) 16 | swapParams(ast, "Take", 2) 17 | swapParams(ast, "Fiber", 2) 18 | swapParams(ast, "FiberRuntime", 2) 19 | swapParams(ast, "Request", 2) 20 | swapParams(ast, "Resource", 2) 21 | swapParams(ast, "TExit", 2) 22 | swapParams(ast, "Deferred", 2) 23 | swapParams(ast, "TDeferred", 2) 24 | swapParams(ast, "Pool", 2) 25 | swapSchema(ast, j) 26 | swapChannel(ast, j) 27 | }) 28 | 29 | root.find(j.VariableDeclaration).forEach(ast => { 30 | fixTagIdentifier(ast, j) 31 | }) 32 | 33 | root.find(j.CallExpression).forEach(ast => { 34 | swapFunctionCall(ast, "async", 3) 35 | swapFunctionCall(ast, "asyncEffect", 3) 36 | swapFunctionCall(ast, "asyncEither", 3) 37 | swapFunctionCall(ast, "asyncInterrupt", 3) 38 | swapFunctionCall(ast, "asyncOption", 3) 39 | swapFunctionCall(ast, "asyncScoped", 3) 40 | 41 | swapMethodCall(ast, "Deferred", "make", 2) 42 | swapMethodCall(ast, "Deferred", "makeAs", 2) 43 | swapMethodCall(ast, "Deferred", "unsafeMake", 2) 44 | 45 | if (expressionHasName(ast.value.callee, "Tag")) { 46 | expressionRename(ast.value.callee, "GenericTag") 47 | } 48 | }) 49 | 50 | root 51 | .find(j.ImportDeclaration) 52 | .filter(_ => _.node.source.value === "effect/Context") 53 | .find(j.ImportSpecifier) 54 | .filter(_ => _.node.imported.name === "Tag") 55 | .replaceWith(j.importSpecifier(j.identifier("GenericTag"))) 56 | 57 | return root.toSource() 58 | } 59 | 60 | // 61 | // utilities 62 | // 63 | 64 | const swapParams = ( 65 | ast: cs.ASTPath, 66 | name: string, 67 | size: number, 68 | ) => { 69 | if (hasName(ast, name) && ast.value.typeParameters?.params.length === size) { 70 | const params = ast.value.typeParameters.params 71 | params.reverse() 72 | for (let i = 0; i < size - 1; i++) { 73 | popNever(params) 74 | } 75 | } 76 | } 77 | 78 | const swapSchema = ( 79 | ast: cs.ASTPath, 80 | j: cs.API["jscodeshift"], 81 | ) => { 82 | if (hasName(ast, "Schema") && ast.value.typeParameters?.params.length === 3) { 83 | const params = ast.value.typeParameters.params 84 | params.reverse() 85 | popNever(params) 86 | if ( 87 | params.length === 2 88 | && j(params[0]).toSource() === j(params[1]).toSource() 89 | ) { 90 | params.pop() 91 | } 92 | } 93 | } 94 | 95 | const swapFunctionCall = ( 96 | ast: cs.ASTPath, 97 | name: string, 98 | size: number, 99 | ) => { 100 | if ( 101 | ast.value.typeParameters?.params.length === size 102 | && expressionHasName(ast.value.callee, name) 103 | ) { 104 | ast.value.typeParameters.params.reverse() 105 | for (let i = 0; i < size - 1; i++) { 106 | popNever(ast.value.typeParameters.params) 107 | } 108 | } 109 | } 110 | 111 | const swapMethodCall = ( 112 | ast: cs.ASTPath, 113 | object: string, 114 | name: string, 115 | size: number, 116 | ) => { 117 | if ( 118 | ast.value.typeParameters?.params.length === size 119 | && expressionHasPropAccess(ast.value.callee, object, name) 120 | ) { 121 | ast.value.typeParameters.params.reverse() 122 | for (let i = 0; i < size - 1; i++) { 123 | popNever(ast.value.typeParameters.params) 124 | } 125 | } 126 | } 127 | 128 | const popNever = (params: Array) => { 129 | if ( 130 | params.length > 0 131 | && params[params.length - 1].type === "TSNeverKeyword" 132 | ) { 133 | params.pop() 134 | } 135 | } 136 | 137 | const popDefaults = (size: number, defaults: ReadonlyArray) => { 138 | const defaultDiff = size - defaults.length 139 | return (j: cs.API["jscodeshift"], params: Array) => { 140 | for (let i = params.length; i > 0; i--) { 141 | const def = defaults[i - 1 - defaultDiff] 142 | const param = params[i - 1] 143 | if (j(param).toSource() === def) { 144 | params.pop() 145 | } else { 146 | break 147 | } 148 | } 149 | } 150 | } 151 | 152 | const popChannelDefaults = popDefaults(7, [ 153 | "unknown", 154 | "never", 155 | "unknown", 156 | "void", 157 | "unknown", 158 | "never", 159 | ]) 160 | const swapChannel = ( 161 | ast: cs.ASTPath, 162 | j: cs.API["jscodeshift"], 163 | ) => { 164 | if ( 165 | hasName(ast, "Channel") && ast.value.typeParameters?.params.length === 7 166 | ) { 167 | const params = ast.value.typeParameters.params 168 | const newParams = [ 169 | params[5], 170 | params[2], 171 | params[4], 172 | params[1], 173 | params[6], 174 | params[3], 175 | params[0], 176 | ] 177 | popChannelDefaults(j, newParams) 178 | ast.value.typeParameters.params = newParams 179 | } 180 | } 181 | 182 | const expressionHasName = (ast: k.ExpressionKind, name: string): boolean => { 183 | switch (ast.type) { 184 | case "Identifier": { 185 | return ast.name === name 186 | } 187 | case "MemberExpression": { 188 | return expressionHasName(ast.property, name) 189 | } 190 | default: { 191 | return false 192 | } 193 | } 194 | } 195 | 196 | const expressionHasPropAccess = ( 197 | ast: k.ExpressionKind, 198 | object: string, 199 | prop: string, 200 | ): boolean => 201 | ast.type === "MemberExpression" 202 | && ast.object.type === "Identifier" && ast.object.name === object 203 | && ast.property.type === "Identifier" && ast.property.name === prop 204 | 205 | const expressionRename = (ast: k.ExpressionKind, name: string): void => { 206 | switch (ast.type) { 207 | case "Identifier": { 208 | ast.name = name 209 | return 210 | } 211 | case "MemberExpression": { 212 | return expressionRename(ast.property, name) 213 | } 214 | default: { 215 | return 216 | } 217 | } 218 | } 219 | 220 | const hasName = (reference: cs.ASTPath, name: string) => { 221 | const initial = reference.value.typeName 222 | const loop = (node: typeof initial): boolean => { 223 | switch (node.type) { 224 | case "Identifier": { 225 | return node.name === name 226 | } 227 | case "JSXIdentifier": { 228 | return false 229 | } 230 | case "TSQualifiedName": { 231 | return loop(node.right) 232 | } 233 | case "TSTypeParameter": { 234 | return false 235 | } 236 | } 237 | } 238 | return loop(initial) 239 | } 240 | 241 | const fixTagIdentifier = ( 242 | ast: cs.ASTPath, 243 | j: cs.API["jscodeshift"], 244 | ) => { 245 | if (ast.value.declarations.length === 1) { 246 | const declaration = ast.value.declarations[0] 247 | if ( 248 | declaration.type === "VariableDeclarator" 249 | && declaration.init 250 | && declaration.init.type === "CallExpression" 251 | ) { 252 | const init = declaration.init 253 | const callee = init.callee 254 | if ( 255 | expressionHasName(callee, "Tag") 256 | && init.arguments.length === 0 257 | && declaration.id.type === "Identifier" 258 | ) { 259 | init.arguments.push(j.stringLiteral(`@services/${declaration.id.name}`)) 260 | } 261 | } 262 | } 263 | } 264 | 265 | // 266 | // this is needed to resolve a bug in jscodeshift that 267 | // forgets to traverse type parameters in call expressions 268 | // 269 | 270 | declare module "ast-types/gen/namedTypes" { 271 | namespace namedTypes { 272 | interface CallExpression extends TSHasOptionalTypeParameterInstantiation {} 273 | } 274 | } 275 | 276 | const forEveryTypeReference = ( 277 | node: Collection, 278 | j: cs.API["jscodeshift"], 279 | f: (ast: cs.ASTPath) => void, 280 | ) => { 281 | const visited = new Set() 282 | node.find(j.TSTypeReference).forEach(ast => { 283 | if (!visited.has(ast)) { 284 | visited.add(ast) 285 | f(ast) 286 | } 287 | }) 288 | node.find(j.CallExpression).forEach(path => { 289 | const typeParams = path.value.typeParameters 290 | if (typeParams) { 291 | j(typeParams).find(j.TSTypeReference).forEach(ast => { 292 | if (!visited.has(ast)) { 293 | visited.add(ast) 294 | f(ast) 295 | } 296 | }) 297 | } 298 | }) 299 | } 300 | -------------------------------------------------------------------------------- /public/codemods/schema-0.69.ts: -------------------------------------------------------------------------------- 1 | import type { namedTypes } from "ast-types/gen/namedTypes" 2 | import type { NodePath } from "ast-types/lib/node-path" 3 | import type cs from "jscodeshift" 4 | import * as Utils from "../Utils" 5 | 6 | type ASTPath = NodePath 7 | 8 | export default function transformer(file: cs.FileInfo, api: cs.API) { 9 | const j = api.jscodeshift 10 | 11 | const root = j(file.source) 12 | 13 | const schemaNamespace = Utils.orElse( 14 | Utils.getNamespaceImport(file, api, "@effect/schema/Schema", false), 15 | () => Utils.getNamedImport(file, api, "@effect/schema", "Schema", false), 16 | ) 17 | 18 | if (schemaNamespace) { 19 | // --------------------------------------------------------------- 20 | // Record(key, value) -> Record({ key, value }) 21 | // --------------------------------------------------------------- 22 | 23 | const replaceRecordArguments = ( 24 | path: ASTPath, 25 | ) => { 26 | const args = path.value.arguments 27 | const key = args[0] 28 | const value = args[1] 29 | path.value.arguments = [ 30 | j.objectExpression([ 31 | j.objectProperty(j.identifier("key"), key as any), 32 | j.objectProperty(j.identifier("value"), value as any), 33 | ]), 34 | ] 35 | } 36 | 37 | root.find(j.CallExpression, { 38 | callee: { 39 | type: "MemberExpression", 40 | object: { name: schemaNamespace }, 41 | property: { name: "Record" }, 42 | }, 43 | }).forEach(replaceRecordArguments) 44 | 45 | const directRecordImport = Utils.getNamedImport( 46 | file, 47 | api, 48 | "@effect/schema/Schema", 49 | "Record", 50 | false, 51 | ) 52 | if (directRecordImport) { 53 | root.find(j.CallExpression, { 54 | callee: { 55 | name: directRecordImport, 56 | }, 57 | }).forEach(replaceRecordArguments) 58 | } 59 | 60 | // --------------------------------------------------------------- 61 | // TaggedRequest(failure, success, payload) -> TaggedRequest({ failure, success, payload }) 62 | // --------------------------------------------------------------- 63 | 64 | const replaceTaggedRequestArguments = ( 65 | path: ASTPath, 66 | ) => { 67 | const args = path.value.arguments 68 | const newArgs = j.objectExpression([ 69 | j.property("init", j.identifier("failure"), args[1] as any), 70 | j.property("init", j.identifier("success"), args[2] as any), 71 | j.property("init", j.identifier("payload"), args[3] as any), 72 | ]) 73 | path.value.arguments = [args[0], newArgs] 74 | } 75 | 76 | root.find(j.ClassDeclaration).forEach(path => { 77 | return j(path).find(j.CallExpression, { 78 | callee: { 79 | type: "CallExpression", 80 | callee: { 81 | type: "MemberExpression", 82 | object: { 83 | name: schemaNamespace, 84 | }, 85 | property: { 86 | name: "TaggedRequest", 87 | }, 88 | }, 89 | }, 90 | }).forEach(replaceTaggedRequestArguments) 91 | }) 92 | 93 | const directTaggedRequestImport = Utils.getNamedImport( 94 | file, 95 | api, 96 | "@effect/schema/Schema", 97 | "TaggedRequest", 98 | false, 99 | ) 100 | if (directTaggedRequestImport) { 101 | root.find(j.CallExpression, { 102 | callee: { 103 | type: "CallExpression", 104 | callee: { 105 | name: directTaggedRequestImport, 106 | }, 107 | }, 108 | }).forEach(replaceTaggedRequestArguments) 109 | } 110 | 111 | // --------------------------------------------------------------- 112 | // Schema.NonEmpty -> Schema.NonEmptyString 113 | // --------------------------------------------------------------- 114 | 115 | Utils.renameMembers( 116 | api, 117 | root, 118 | schemaNamespace, 119 | "NonEmpty", 120 | "NonEmptyString", 121 | ) 122 | 123 | // --------------------------------------------------------------- 124 | // Schema.nonEmpty() -> Schema.nonEmptyString() 125 | // --------------------------------------------------------------- 126 | 127 | Utils.renameMembers( 128 | api, 129 | root, 130 | schemaNamespace, 131 | "nonEmpty", 132 | "nonEmptyString", 133 | ) 134 | 135 | // --------------------------------------------------------------- 136 | // Schema.optional({ ... }) -> Schema.optionalWith({ ... }) 137 | // Schema.optional(schema, { ... }) -> Schema.optionalWith(schema, { ... }) 138 | // --------------------------------------------------------------- 139 | 140 | root.find(j.CallExpression, { 141 | callee: { 142 | type: "MemberExpression", 143 | object: { name: schemaNamespace }, 144 | property: { name: "optional" }, 145 | }, 146 | }).forEach(path => { 147 | const args = path.value.arguments 148 | if ( 149 | (args.length === 1 && args[0].type === "ObjectExpression") 150 | || (args.length === 2 && args[1].type === "ObjectExpression") 151 | ) { 152 | path.value.callee = j.memberExpression( 153 | j.identifier(schemaNamespace), 154 | j.identifier("optionalWith"), 155 | ) 156 | } 157 | }) 158 | 159 | // --------------------------------------------------------------- 160 | // Schema.optional() -> Schema.optional 161 | // --------------------------------------------------------------- 162 | 163 | root.find(j.CallExpression, { 164 | callee: { 165 | type: "MemberExpression", 166 | object: { name: schemaNamespace }, 167 | property: { name: "optional" }, 168 | }, 169 | }).forEach(path => { 170 | const args = path.value.arguments 171 | if (args.length === 0) { 172 | path.replace(j.memberExpression( 173 | j.identifier(schemaNamespace), 174 | j.identifier("optional"), 175 | )) 176 | } 177 | }) 178 | 179 | // --------------------------------------------------------------- 180 | // Schema.partial({ ... }) -> Schema.partialWith({ ... }) 181 | // Schema.partial(schema, { ... }) -> Schema.partialWith(schema, { ... }) 182 | // --------------------------------------------------------------- 183 | 184 | root.find(j.CallExpression, { 185 | callee: { 186 | type: "MemberExpression", 187 | object: { name: schemaNamespace }, 188 | property: { name: "partial" }, 189 | }, 190 | }).forEach(path => { 191 | const args = path.value.arguments 192 | if ( 193 | (args.length === 1 && args[0].type === "ObjectExpression") 194 | || (args.length === 2 && args[1].type === "ObjectExpression") 195 | ) { 196 | path.value.callee = j.memberExpression( 197 | j.identifier(schemaNamespace), 198 | j.identifier("partialWith"), 199 | ) 200 | } 201 | }) 202 | 203 | // --------------------------------------------------------------- 204 | // Schema.partial() -> Schema.partial 205 | // --------------------------------------------------------------- 206 | 207 | root.find(j.CallExpression, { 208 | callee: { 209 | type: "MemberExpression", 210 | object: { name: schemaNamespace }, 211 | property: { name: "partial" }, 212 | }, 213 | }).forEach(path => { 214 | const args = path.value.arguments 215 | if (args.length === 0) { 216 | path.replace(j.memberExpression( 217 | j.identifier(schemaNamespace), 218 | j.identifier("partial"), 219 | )) 220 | } 221 | }) 222 | 223 | // --------------------------------------------------------------- 224 | // Base64 -> Uint8ArrayFromBase64 225 | // --------------------------------------------------------------- 226 | 227 | Utils.renameMembers( 228 | api, 229 | root, 230 | schemaNamespace, 231 | "Base64", 232 | "Uint8ArrayFromBase64", 233 | ) 234 | 235 | // --------------------------------------------------------------- 236 | // Base64Url -> Uint8ArrayFromBase64Url 237 | // --------------------------------------------------------------- 238 | 239 | Utils.renameMembers( 240 | api, 241 | root, 242 | schemaNamespace, 243 | "Base64Url", 244 | "Uint8ArrayFromBase64Url", 245 | ) 246 | 247 | // --------------------------------------------------------------- 248 | // Hex -> Uint8ArrayFromHex 249 | // --------------------------------------------------------------- 250 | 251 | Utils.renameMembers( 252 | api, 253 | root, 254 | schemaNamespace, 255 | "Hex", 256 | "Uint8ArrayFromHex", 257 | ) 258 | 259 | // --------------------------------------------------------------- 260 | // CauseDefectUnknown -> Defect 261 | // --------------------------------------------------------------- 262 | 263 | Utils.renameMembers( 264 | api, 265 | root, 266 | schemaNamespace, 267 | "CauseDefectUnknown", 268 | "Defect", 269 | ) 270 | 271 | // --------------------------------------------------------------- 272 | // ExitFromSelf, Exit, CauseFromSelf, Cause 273 | // --------------------------------------------------------------- 274 | 275 | const addDefect = (api: string) => 276 | root.find(j.CallExpression, { 277 | callee: { 278 | type: "MemberExpression", 279 | object: { name: schemaNamespace }, 280 | property: { name: api }, 281 | }, 282 | }).forEach(path => { 283 | const args = path.value.arguments 284 | if (args.length === 1 && args[0].type === "ObjectExpression") { 285 | const properties = args[0].properties 286 | if ( 287 | !properties.some(prop => { 288 | return prop.type === "ObjectProperty" 289 | && prop.key.type === "Identifier" 290 | && prop.key.name === "defect" 291 | }) 292 | ) { 293 | properties.push( 294 | j.property( 295 | "init", 296 | j.identifier("defect"), 297 | j.memberExpression( 298 | j.identifier(schemaNamespace), 299 | j.identifier("Defect"), 300 | ), 301 | ), 302 | ) 303 | } 304 | } 305 | }) 306 | 307 | addDefect("ExitFromSelf") 308 | addDefect("Exit") 309 | addDefect("CauseFromSelf") 310 | addDefect("Cause") 311 | } 312 | 313 | return root.toSource() 314 | } 315 | -------------------------------------------------------------------------------- /test/schema-0.69.test.ts: -------------------------------------------------------------------------------- 1 | import * as TestUtils from "jscodeshift/src/testUtils" 2 | import { describe } from "vitest" 3 | 4 | import transformer from "../public/codemods/schema-0.69" 5 | 6 | const expectTransformation = ( 7 | description: string, 8 | input: string, 9 | output: string, 10 | ) => { 11 | TestUtils.defineInlineTest( 12 | { default: transformer, parser: "ts" }, 13 | {}, 14 | input, 15 | output, 16 | description, 17 | ) 18 | } 19 | 20 | describe("Record", () => { 21 | expectTransformation( 22 | "named import", 23 | `import { Schema } from "@effect/schema" 24 | const schema = Schema.Record(Schema.String, Schema.Number)`, 25 | `import { Schema } from "@effect/schema" 26 | const schema = Schema.Record({ 27 | key: Schema.String, 28 | value: Schema.Number 29 | })`, 30 | ) 31 | expectTransformation( 32 | "named import renamed", 33 | `import { Schema as S } from "@effect/schema" 34 | const schema = S.Record(S.String, S.Number)`, 35 | `import { Schema as S } from "@effect/schema" 36 | const schema = S.Record({ 37 | key: S.String, 38 | value: S.Number 39 | })`, 40 | ) 41 | expectTransformation( 42 | "namespace import", 43 | `import * as Schema from "@effect/schema/Schema" 44 | const schema = Schema.Record(Schema.String, Schema.Number)`, 45 | `import * as Schema from "@effect/schema/Schema" 46 | const schema = Schema.Record({ 47 | key: Schema.String, 48 | value: Schema.Number 49 | })`, 50 | ) 51 | expectTransformation( 52 | "namespace import renamed", 53 | `import * as S from "@effect/schema/Schema" 54 | const schema = S.Record(S.String, S.Number)`, 55 | `import * as S from "@effect/schema/Schema" 56 | const schema = S.Record({ 57 | key: S.String, 58 | value: S.Number 59 | })`, 60 | ) 61 | expectTransformation( 62 | "direct import", 63 | `import { Record, String, Number } from "@effect/schema/Schema" 64 | const schema = Record(String, Number)`, 65 | `import { Record, String, Number } from "@effect/schema/Schema" 66 | const schema = Record({ 67 | key: String, 68 | value: Number 69 | })`, 70 | ) 71 | expectTransformation( 72 | "direct import renamed", 73 | `import { Record as R, String, Number } from "@effect/schema/Schema" 74 | const schema = R(String, Number)`, 75 | `import { Record as R, String, Number } from "@effect/schema/Schema" 76 | const schema = R({ 77 | key: String, 78 | value: Number 79 | })`, 80 | ) 81 | expectTransformation( 82 | "nested", 83 | `import { Schema } from "@effect/schema" 84 | const schema = Schema.Struct({ a: Schema.Record(Schema.String, Schema.Number) })`, 85 | `import { Schema } from "@effect/schema" 86 | const schema = Schema.Struct({ a: Schema.Record({ 87 | key: Schema.String, 88 | value: Schema.Number 89 | }) })`, 90 | ) 91 | }) 92 | 93 | describe("TaggedRequest", () => { 94 | expectTransformation( 95 | "named import", 96 | `import { Schema } from "@effect/schema" 97 | class UserList extends Schema.TaggedRequest()( 98 | "UserList", 99 | Schema.String, 100 | Schema.Array(User), 101 | {} 102 | ) {}`, 103 | `import { Schema } from "@effect/schema" 104 | class UserList extends Schema.TaggedRequest()("UserList", { 105 | failure: Schema.String, 106 | success: Schema.Array(User), 107 | payload: {} 108 | }) {}`, 109 | ) 110 | expectTransformation( 111 | "direct import renamed", 112 | `import { TaggedRequest as TR, String, Array } from "@effect/schema/Schema" 113 | class UserList extends TR()( 114 | "UserList", 115 | String, 116 | Array(User), 117 | {} 118 | ) {}`, 119 | `import { TaggedRequest as TR, String, Array } from "@effect/schema/Schema" 120 | class UserList extends TR()("UserList", { 121 | failure: String, 122 | success: Array(User), 123 | payload: {} 124 | }) {}`, 125 | ) 126 | }) 127 | 128 | describe("NonEmpty", () => { 129 | expectTransformation( 130 | "named import", 131 | `import { Schema } from "@effect/schema" 132 | const schema = Schema.NonEmpty`, 133 | `import { Schema } from "@effect/schema" 134 | const schema = Schema.NonEmptyString`, 135 | ) 136 | }) 137 | 138 | describe("nonEmpty()", () => { 139 | expectTransformation( 140 | "named import", 141 | `import { Schema } from "@effect/schema" 142 | const schema = Schema.String.pipe(Schema.nonEmpty())`, 143 | `import { Schema } from "@effect/schema" 144 | const schema = Schema.String.pipe(Schema.nonEmptyString())`, 145 | ) 146 | }) 147 | 148 | describe("optional()", () => { 149 | expectTransformation( 150 | "named import", 151 | `import { Schema } from "@effect/schema" 152 | const schema = Schema.String.pipe(Schema.optional())`, 153 | `import { Schema } from "@effect/schema" 154 | const schema = Schema.String.pipe(Schema.optional)`, 155 | ) 156 | expectTransformation( 157 | "named import", 158 | `import { Schema } from "@effect/schema" 159 | const schema = Schema.optional(Schema.String)`, 160 | `import { Schema } from "@effect/schema" 161 | const schema = Schema.optional(Schema.String)`, 162 | ) 163 | }) 164 | 165 | describe("optional({ ... }) / optional(schema, { ... })", () => { 166 | expectTransformation( 167 | "named import", 168 | `import { Schema } from "@effect/schema" 169 | const schema = Schema.String.pipe(Schema.optional({ exact: true }))`, 170 | `import { Schema } from "@effect/schema" 171 | const schema = Schema.String.pipe(Schema.optionalWith({ exact: true }))`, 172 | ) 173 | expectTransformation( 174 | "named import", 175 | `import { Schema } from "@effect/schema" 176 | const schema = Schema.optional(Schema.String, { exact: true })`, 177 | `import { Schema } from "@effect/schema" 178 | const schema = Schema.optionalWith(Schema.String, { exact: true })`, 179 | ) 180 | }) 181 | 182 | describe("partial()", () => { 183 | expectTransformation( 184 | "named import", 185 | `import { Schema } from "@effect/schema" 186 | const schema = Schema.Struct({ a: Schema.Number }).pipe(Schema.partial())`, 187 | `import { Schema } from "@effect/schema" 188 | const schema = Schema.Struct({ a: Schema.Number }).pipe(Schema.partial)`, 189 | ) 190 | expectTransformation( 191 | "named import", 192 | `import { Schema as S } from "@effect/schema" 193 | const schema = S.Struct({ a: S.Number }).pipe(S.partial())`, 194 | `import { Schema as S } from "@effect/schema" 195 | const schema = S.Struct({ a: S.Number }).pipe(S.partial)`, 196 | ) 197 | expectTransformation( 198 | "named import", 199 | `import { Schema } from "@effect/schema" 200 | const schema = Schema.partial(Schema.Struct({ a: Schema.Number }))`, 201 | `import { Schema } from "@effect/schema" 202 | const schema = Schema.partial(Schema.Struct({ a: Schema.Number }))`, 203 | ) 204 | }) 205 | 206 | describe("partial({ ... }) / optional(partial, { ... })", () => { 207 | expectTransformation( 208 | "named import", 209 | `import { Schema } from "@effect/schema" 210 | const schema = Schema.Struct({ a: Schema.Number }).pipe(Schema.partial({ exact: true }))`, 211 | `import { Schema } from "@effect/schema" 212 | const schema = Schema.Struct({ a: Schema.Number }).pipe(Schema.partialWith({ exact: true }))`, 213 | ) 214 | expectTransformation( 215 | "named import", 216 | `import { Schema } from "@effect/schema" 217 | const schema = Schema.partial(Schema.Struct({ a: Schema.Number }), { exact: true })`, 218 | `import { Schema } from "@effect/schema" 219 | const schema = Schema.partialWith(Schema.Struct({ a: Schema.Number }), { exact: true })`, 220 | ) 221 | }) 222 | 223 | describe("Base64 -> Uint8ArrayFromBase64", () => { 224 | expectTransformation( 225 | "named import", 226 | `import { Schema } from "@effect/schema" 227 | const schema = Schema.Base64`, 228 | `import { Schema } from "@effect/schema" 229 | const schema = Schema.Uint8ArrayFromBase64`, 230 | ) 231 | }) 232 | 233 | describe("Base64Url -> Uint8ArrayFromBase64Url", () => { 234 | expectTransformation( 235 | "named import", 236 | `import { Schema } from "@effect/schema" 237 | const schema = Schema.Base64Url`, 238 | `import { Schema } from "@effect/schema" 239 | const schema = Schema.Uint8ArrayFromBase64Url`, 240 | ) 241 | }) 242 | 243 | describe("Hex -> Uint8ArrayFromHex", () => { 244 | expectTransformation( 245 | "named import", 246 | `import { Schema } from "@effect/schema" 247 | const schema = Schema.Hex`, 248 | `import { Schema } from "@effect/schema" 249 | const schema = Schema.Uint8ArrayFromHex`, 250 | ) 251 | }) 252 | 253 | describe("CauseDefectUnknown -> Defect", () => { 254 | expectTransformation( 255 | "named import", 256 | `import { Schema } from "@effect/schema" 257 | const schema = Schema.CauseDefectUnknown`, 258 | `import { Schema } from "@effect/schema" 259 | const schema = Schema.Defect`, 260 | ) 261 | }) 262 | 263 | describe("ExitFromSelf", () => { 264 | expectTransformation( 265 | "explicit defect", 266 | `import { Schema as S } from "@effect/schema" 267 | const schema = S.ExitFromSelf({ failure: S.String, success: S.Number, defect: S.Unknown })`, 268 | `import { Schema as S } from "@effect/schema" 269 | const schema = S.ExitFromSelf({ failure: S.String, success: S.Number, defect: S.Unknown })`, 270 | ) 271 | expectTransformation( 272 | "explicit defect with CauseDefectUnknown", 273 | `import { Schema as S } from "@effect/schema" 274 | const schema = S.ExitFromSelf({ failure: S.String, success: S.Number, defect: S.CauseDefectUnknown })`, 275 | `import { Schema as S } from "@effect/schema" 276 | const schema = S.ExitFromSelf({ failure: S.String, success: S.Number, defect: S.Defect })`, 277 | ) 278 | expectTransformation( 279 | "implicit defect", 280 | `import { Schema as S } from "@effect/schema" 281 | const schema = S.ExitFromSelf({ failure: S.String, success: S.Number })`, 282 | `import { Schema as S } from "@effect/schema" 283 | const schema = S.ExitFromSelf({ 284 | failure: S.String, 285 | success: S.Number, 286 | defect: S.Defect 287 | })`, 288 | ) 289 | }) 290 | 291 | describe("Exit", () => { 292 | expectTransformation( 293 | "explicit defect", 294 | `import { Schema as S } from "@effect/schema" 295 | const schema = S.Exit({ failure: S.String, success: S.Number, defect: S.Unknown })`, 296 | `import { Schema as S } from "@effect/schema" 297 | const schema = S.Exit({ failure: S.String, success: S.Number, defect: S.Unknown })`, 298 | ) 299 | expectTransformation( 300 | "implicit defect", 301 | `import { Schema as S } from "@effect/schema" 302 | const schema = S.Exit({ failure: S.String, success: S.Number })`, 303 | `import { Schema as S } from "@effect/schema" 304 | const schema = S.Exit({ 305 | failure: S.String, 306 | success: S.Number, 307 | defect: S.Defect 308 | })`, 309 | ) 310 | }) 311 | 312 | describe("CauseFromSelf", () => { 313 | expectTransformation( 314 | "explicit defect", 315 | `import { Schema as S } from "@effect/schema" 316 | const schema = S.CauseFromSelf({ error: S.NumberFromString, defect: S.Unknown })`, 317 | `import { Schema as S } from "@effect/schema" 318 | const schema = S.CauseFromSelf({ error: S.NumberFromString, defect: S.Unknown })`, 319 | ) 320 | expectTransformation( 321 | "implicit defect", 322 | `import { Schema as S } from "@effect/schema" 323 | const schema = S.CauseFromSelf({ error: S.NumberFromString })`, 324 | `import { Schema as S } from "@effect/schema" 325 | const schema = S.CauseFromSelf({ 326 | error: S.NumberFromString, 327 | defect: S.Defect 328 | })`, 329 | ) 330 | }) 331 | 332 | describe("Cause", () => { 333 | expectTransformation( 334 | "explicit defect", 335 | `import { Schema as S } from "@effect/schema" 336 | const schema = S.Cause({ error: S.NumberFromString, defect: S.Unknown })`, 337 | `import { Schema as S } from "@effect/schema" 338 | const schema = S.Cause({ error: S.NumberFromString, defect: S.Unknown })`, 339 | ) 340 | expectTransformation( 341 | "implicit defect", 342 | `import { Schema as S } from "@effect/schema" 343 | const schema = S.Cause({ error: S.NumberFromString })`, 344 | `import { Schema as S } from "@effect/schema" 345 | const schema = S.Cause({ 346 | error: S.NumberFromString, 347 | defect: S.Defect 348 | })`, 349 | ) 350 | }) 351 | -------------------------------------------------------------------------------- /public/codemods/schema-0.65.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * test: npx jscodeshift -d -p -t ./public/codemods/schema-0.65.ts test/schema-0.65/NamespaceImport.ts 3 | */ 4 | 5 | import type { ExpressionKind } from "ast-types/gen/kinds" 6 | import type { namedTypes } from "ast-types/gen/namedTypes" 7 | import type { NodePath } from "ast-types/lib/node-path" 8 | import type cs from "jscodeshift" 9 | import type { Collection } from "jscodeshift/src/Collection" 10 | 11 | type ASTPath = NodePath 12 | 13 | export default function transformer(file: cs.FileInfo, api: cs.API) { 14 | const j = api.jscodeshift 15 | 16 | const root = j(file.source) 17 | 18 | const schemaNamespace = orElse( 19 | findNamespaceImport(file, api, "@effect/schema/Schema"), 20 | () => findNamedImport(file, api, "@effect/schema", "Schema"), 21 | ) 22 | 23 | if (schemaNamespace !== undefined) { 24 | // struct -> Struct, string -> String, etc... 25 | root 26 | .find(j.MemberExpression, { object: { name: schemaNamespace } }) 27 | .forEach(path => { 28 | const expr = path.value 29 | const property = expr.property 30 | if (property.type === "Identifier") { 31 | const name = property.name 32 | // handle API with /bigint/ig 33 | if (/bigint/ig.test(name)) { 34 | property.name = property.name.replace(/bigint/ig, "BigInt") 35 | } else if (isSchemaNameChanged(name)) { 36 | const value: string | null = schemaChangedNames[name] 37 | property.name = value === null 38 | ? name.charAt(0).toUpperCase() + name.slice(1) 39 | : value 40 | } 41 | } 42 | }) 43 | 44 | root 45 | .find(j.TSTypeReference, { 46 | typeName: { left: { name: schemaNamespace } }, 47 | }) 48 | .forEach(path => { 49 | const expr = path.value 50 | const typeName = expr.typeName 51 | if (typeName.type === "TSQualifiedName") { 52 | const right = typeName.right 53 | if (right.type === "Identifier") { 54 | const name = right.name 55 | if (/bigint/ig.test(name)) { 56 | right.name = right.name.replace(/bigint/ig, "BigInt") 57 | } else if (isTypeLevelSchemaNameChanged(name)) { 58 | const value: string | null = typeLevelSchemaChangedNames[name] 59 | right.name = value === null 60 | ? name.charAt(0).toUpperCase() + name.slice(1) 61 | : value 62 | } 63 | } 64 | } 65 | }) 66 | 67 | // const changeArguments = ( 68 | // api: string, 69 | // changes: Record, 70 | // ) => { 71 | // const calls = root.find(j.CallExpression, { 72 | // callee: { 73 | // type: "MemberExpression", 74 | // object: { name: schemaNamespace }, 75 | // property: { name: api }, 76 | // }, 77 | // }) 78 | // calls.forEach(callExpression => { 79 | // const arg = callExpression.node.arguments[0] 80 | // if (arg.type === "ObjectExpression") { 81 | // arg.properties.forEach(property => { 82 | // if (property.type === "ObjectProperty") { 83 | // if (property.key.type === "Identifier") { 84 | // const key = property.key.name 85 | // if (key in changes) { 86 | // property.key.name = changes[key] 87 | // } 88 | // } 89 | // } 90 | // }) 91 | // } 92 | // }) 93 | // } 94 | 95 | // changeArguments("EitherFromSelf", { right: "Right", left: "Left" }) 96 | // changeArguments("Either", { right: "Right", left: "Left" }) 97 | // changeArguments("EitherFromUnion", { right: "Right", left: "Left" }) 98 | // changeArguments("ReadonlyMapFromSelf", { key: "Key", value: "Value" }) 99 | // changeArguments("MapFromSelf", { key: "Key", value: "Value" }) 100 | // changeArguments("ReadonlyMap", { key: "Key", value: "Value" }) 101 | // changeArguments("Map", { key: "Key", value: "Value" }) 102 | // changeArguments("CauseFromSelf", { error: "Error", defect: "Defect" }) 103 | // changeArguments("Cause", { error: "Error", defect: "Defect" }) 104 | // changeArguments("ExitFromSelf", { 105 | // failure: "Failure", 106 | // success: "Success", 107 | // defect: "Defect", 108 | // }) 109 | // changeArguments("Exit", { 110 | // failure: "Failure", 111 | // success: "Success", 112 | // defect: "Defect", 113 | // }) 114 | // changeArguments("HashMapFromSelf", { key: "Key", value: "Value" }) 115 | // changeArguments("HashMap", { key: "Key", value: "Value" }) 116 | 117 | const getDecodeEncodeOptions = ( 118 | decode: ExpressionKind, 119 | encode: ExpressionKind, 120 | strictFalse: boolean = false, 121 | ) => { 122 | const properties = [ 123 | j.objectProperty(j.identifier("decode"), decode), 124 | j.objectProperty(j.identifier("encode"), encode), 125 | ] 126 | if (strictFalse) { 127 | properties.unshift(j.objectProperty( 128 | j.identifier("strict"), 129 | j.booleanLiteral(false), 130 | )) 131 | } 132 | return j.objectExpression(properties) 133 | } 134 | 135 | const replaceTransformAndTransformOfFailFunctions = ( 136 | path: ASTPath, 137 | ) => { 138 | const args = path.value.arguments 139 | const hasStrictFalse = args[args.length - 1].type === "ObjectExpression" 140 | if (hasStrictFalse) { 141 | if (args.length === 5) { 142 | const decode = args[2] 143 | const encode = args[3] 144 | if ( 145 | decode.type !== "SpreadElement" 146 | && encode.type !== "SpreadElement" 147 | ) { 148 | args.splice(2, 3, getDecodeEncodeOptions(decode, encode, true)) 149 | } 150 | } else if (args.length === 4) { 151 | const decode = args[1] 152 | const encode = args[2] 153 | if ( 154 | decode.type !== "SpreadElement" 155 | && encode.type !== "SpreadElement" 156 | ) { 157 | args.splice(1, 3, getDecodeEncodeOptions(decode, encode, true)) 158 | } 159 | } 160 | } else { 161 | if (args.length === 4) { 162 | const decode = args[2] 163 | const encode = args[3] 164 | if ( 165 | decode.type !== "SpreadElement" 166 | && encode.type !== "SpreadElement" 167 | ) { 168 | args.splice(2, 2, getDecodeEncodeOptions(decode, encode)) 169 | } 170 | } else if (args.length === 3) { 171 | const decode = args[1] 172 | const encode = args[2] 173 | if ( 174 | decode.type !== "SpreadElement" 175 | && encode.type !== "SpreadElement" 176 | ) { 177 | args.splice(1, 2, getDecodeEncodeOptions(decode, encode)) 178 | } 179 | } 180 | } 181 | } 182 | 183 | const find = ( 184 | name: string, 185 | f: ( 186 | path: ASTPath, 187 | ) => void, 188 | ) => 189 | root.find(j.CallExpression, { 190 | callee: { 191 | type: "MemberExpression", 192 | object: { name: schemaNamespace }, 193 | property: { name }, 194 | }, 195 | }).forEach(f) 196 | 197 | find("transform", replaceTransformAndTransformOfFailFunctions) 198 | find("transformOrFail", replaceTransformAndTransformOfFailFunctions) 199 | 200 | find("declare", path => { 201 | const args = path.value.arguments 202 | if (args.length >= 3) { 203 | const decode = args[1] 204 | const encode = args[2] 205 | if ( 206 | decode.type !== "SpreadElement" 207 | && encode.type !== "SpreadElement" 208 | ) { 209 | args.splice(1, 2, getDecodeEncodeOptions(decode, encode)) 210 | } 211 | } 212 | }) 213 | 214 | const replaceOptionalToFunctions = ( 215 | path: ASTPath, 216 | ) => { 217 | const args = path.value.arguments 218 | const decode = args[2] 219 | const encode = args[3] 220 | if ( 221 | decode.type !== "SpreadElement" 222 | && encode.type !== "SpreadElement" 223 | ) { 224 | args.splice(2, 2, getDecodeEncodeOptions(decode, encode)) 225 | } 226 | } 227 | 228 | find("optionalToRequired", replaceOptionalToFunctions) 229 | find("optionalToOptional", replaceOptionalToFunctions) 230 | 231 | // Class.transformOrFail / Class.transformOrFrom 232 | root.find(j.ClassDeclaration).forEach(classDeclaration => { 233 | const superClass = classDeclaration.node.superClass 234 | if (superClass) { 235 | if (superClass.type === "CallExpression") { 236 | const callee = superClass.callee 237 | if (callee.type === "CallExpression") { 238 | if (callee.callee.type === "MemberExpression") { 239 | const property = callee.callee.property 240 | if (property.type === "Identifier") { 241 | if ( 242 | property.name === "transformOrFail" 243 | || property.name === "transformOrFailFrom" 244 | ) { 245 | callee.type 246 | const args = superClass.arguments 247 | if (args.length === 3) { 248 | const decode = args[1] 249 | const encode = args[2] 250 | if ( 251 | decode.type !== "SpreadElement" 252 | && encode.type !== "SpreadElement" 253 | ) { 254 | args.splice(1, 2, getDecodeEncodeOptions(decode, encode)) 255 | } 256 | } 257 | } 258 | } 259 | } 260 | } 261 | } 262 | } 263 | }) 264 | } 265 | 266 | const formatterChanges = (formatterNamespace: string) => { 267 | root 268 | .find(j.MemberExpression, { object: { name: formatterNamespace } }) 269 | .forEach(path => { 270 | const expr = path.value 271 | const property = expr.property 272 | if (property.type === "Identifier") { 273 | const name = property.name 274 | if (isFormatterNameChanged(name)) { 275 | property.name = formatterChangedNames[name] 276 | } 277 | } 278 | }) 279 | } 280 | 281 | const treeFormatterNamespace = orElse( 282 | findNamespaceImport(file, api, "@effect/schema/TreeFormatter"), 283 | () => findNamedImport(file, api, "@effect/schema", "TreeFormatter"), 284 | ) 285 | 286 | if (treeFormatterNamespace !== undefined) { 287 | formatterChanges(treeFormatterNamespace) 288 | } 289 | replaceNamespaceImport( 290 | api, 291 | root, 292 | "@effect/schema/TreeFormatter", 293 | "formatError", 294 | "formatErrorSync", 295 | ) 296 | replaceNamespaceImport( 297 | api, 298 | root, 299 | "@effect/schema/TreeFormatter", 300 | "formatErrorEffect", 301 | "formatError", 302 | ) 303 | 304 | const arrayFormatterNamespace = orElse( 305 | findNamespaceImport(file, api, "@effect/schema/ArrayFormatter"), 306 | () => findNamedImport(file, api, "@effect/schema", "ArrayFormatter"), 307 | ) 308 | 309 | if (arrayFormatterNamespace !== undefined) { 310 | formatterChanges(arrayFormatterNamespace) 311 | } 312 | replaceNamespaceImport( 313 | api, 314 | root, 315 | "@effect/schema/ArrayFormatter", 316 | "formatError", 317 | "formatErrorSync", 318 | ) 319 | replaceNamespaceImport( 320 | api, 321 | root, 322 | "@effect/schema/ArrayFormatter", 323 | "formatErrorEffect", 324 | "formatError", 325 | ) 326 | 327 | const astNamespace = orElse( 328 | findNamespaceImport(file, api, "@effect/schema/AST"), 329 | () => findNamedImport(file, api, "@effect/schema", "AST"), 330 | ) 331 | 332 | const astChanges = (astNamespace: string) => { 333 | root 334 | .find(j.MemberExpression, { object: { name: astNamespace } }) 335 | .forEach(path => { 336 | const expr = path.value 337 | const property = expr.property 338 | if (property.type === "Identifier") { 339 | const name = property.name 340 | if (isASTNameChanged(name)) { 341 | property.name = astChangedNames[name] 342 | } 343 | } 344 | }) 345 | } 346 | 347 | if (astNamespace !== undefined) { 348 | astChanges(astNamespace) 349 | } 350 | 351 | const parseResultNamespace = orElse( 352 | findNamespaceImport(file, api, "@effect/schema/ParseResult"), 353 | () => findNamedImport(file, api, "@effect/schema", "ParseResult"), 354 | ) 355 | 356 | const parseResultChanges = (parseResultNamespace: string) => { 357 | root 358 | .find(j.MemberExpression, { object: { name: parseResultNamespace } }) 359 | .forEach(path => { 360 | const expr = path.value 361 | const property = expr.property 362 | if (property.type === "Identifier") { 363 | const name = property.name 364 | if (isParseResultNameChanged(name)) { 365 | property.name = parseResultChangedNames[name] 366 | } 367 | } 368 | }) 369 | } 370 | 371 | if (parseResultNamespace !== undefined) { 372 | parseResultChanges(parseResultNamespace) 373 | } 374 | 375 | const arbitraryNamespace = orElse( 376 | findNamespaceImport(file, api, "@effect/schema/Arbitrary"), 377 | () => findNamedImport(file, api, "@effect/schema", "Arbitrary"), 378 | ) 379 | 380 | const arbitraryChanges = (arbitraryNamespace: string) => { 381 | root 382 | .find(j.MemberExpression, { object: { name: arbitraryNamespace } }) 383 | .forEach(path => { 384 | const expr = path.value 385 | const property = expr.property 386 | if (property.type === "Identifier") { 387 | const name = property.name 388 | if (isArbitraryNameChanged(name)) { 389 | property.name = arbitraryChangedNames[name] 390 | } 391 | } 392 | }) 393 | } 394 | 395 | const arbitraryTypeLevelChanges = (arbitraryNamespace: string) => { 396 | root 397 | .find(j.TSTypeReference, { 398 | typeName: { left: { name: arbitraryNamespace } }, 399 | }) 400 | .forEach(path => { 401 | const expr = path.value 402 | const typeName = expr.typeName 403 | if (typeName.type === "TSQualifiedName") { 404 | const right = typeName.right 405 | if (right.type === "Identifier") { 406 | const name = right.name 407 | if (isTypeLevelArbitraryNameChanged(name)) { 408 | const value: string | null = typeLevelArbitraryChangedNames[name] 409 | right.name = value === null 410 | ? name.charAt(0).toUpperCase() + name.slice(1) 411 | : value 412 | } 413 | } 414 | } 415 | }) 416 | } 417 | 418 | if (arbitraryNamespace !== undefined) { 419 | arbitraryChanges(arbitraryNamespace) 420 | arbitraryTypeLevelChanges(arbitraryNamespace) 421 | } 422 | 423 | const arbitraryImportDeclarations = root.find(j.ImportDeclaration, { 424 | source: { value: "@effect/schema/Arbitrary" }, 425 | }) 426 | if (arbitraryImportDeclarations.length > 0) { 427 | let name: string | null = null 428 | arbitraryImportDeclarations.forEach(path => { 429 | if (path.value.importKind === "type") { 430 | const specifiers = path.value.specifiers 431 | if (specifiers) { 432 | for (const specifier of specifiers) { 433 | if (specifier.type === "ImportSpecifier") { 434 | if (specifier.imported.name === "Arbitrary" && specifier.local) { 435 | specifier.imported.name = "LazyArbitrary" 436 | name = specifier.local.name 437 | break 438 | } 439 | } 440 | } 441 | } 442 | } 443 | if (name && name === "Arbitrary") { 444 | root 445 | .find(j.TSTypeReference).forEach(path => { 446 | const expr = path.value 447 | const typeName = expr.typeName 448 | if (typeName.type === "Identifier" && typeName.name === name) { 449 | typeName.name = "LazyArbitrary" 450 | } 451 | }) 452 | } 453 | }) 454 | } 455 | 456 | return root.toSource() 457 | } 458 | 459 | const orElse = (x: A | undefined, f: () => A | undefined): A | undefined => 460 | x === undefined ? f() : x 461 | 462 | const findNamespaceImport = ( 463 | file: cs.FileInfo, 464 | api: cs.API, 465 | source: string, 466 | ): string | undefined => { 467 | const j = api.jscodeshift 468 | const importDeclarations = j(file.source).find(j.ImportDeclaration, { 469 | source: { value: source }, 470 | }) 471 | if (importDeclarations.length > 0) { 472 | const name = importDeclarations.find(j.Identifier).get(0).node.name 473 | if (typeof name === "string") { 474 | return name 475 | } 476 | } 477 | return undefined 478 | } 479 | 480 | const replaceNamespaceImport = ( 481 | api: cs.API, 482 | root: Collection, 483 | source: string, 484 | from: string, 485 | to: string, 486 | ) => { 487 | const j = api.jscodeshift 488 | const importDeclarations = root.find(j.ImportDeclaration, { 489 | source: { value: source }, 490 | }) 491 | importDeclarations.forEach(path => { 492 | const specifiers = path.value.specifiers 493 | if (specifiers) { 494 | for (const specifier of specifiers) { 495 | if (specifier.type === "ImportSpecifier") { 496 | if (specifier.imported.name === from && specifier.local) { 497 | specifier.imported.name = to 498 | 499 | root.find(j.CallExpression, { 500 | callee: { name: from }, 501 | }).forEach(path => { 502 | if ( 503 | path.value.callee.type === "Identifier" 504 | && path.value.callee.name === from 505 | ) { 506 | path.value.callee.name = to 507 | } 508 | }) 509 | 510 | break 511 | } 512 | } 513 | } 514 | } 515 | }) 516 | } 517 | 518 | const findNamedImport = ( 519 | file: cs.FileInfo, 520 | api: cs.API, 521 | source: string, 522 | name: string, 523 | ): string | undefined => { 524 | const j = api.jscodeshift 525 | const importDeclarations = j(file.source).find(j.ImportDeclaration, { 526 | source: { value: source }, 527 | }) 528 | if (importDeclarations.length === 0) { 529 | return undefined 530 | } 531 | let out: string = name 532 | importDeclarations.forEach(path => { 533 | const specifiers = path.value.specifiers 534 | if (specifiers) { 535 | for (const specifier of specifiers) { 536 | if (specifier.type === "ImportSpecifier") { 537 | if (specifier.imported.name === name && specifier.local) { 538 | out = specifier.local.name 539 | break 540 | } 541 | } 542 | } 543 | } 544 | }) 545 | return out 546 | } 547 | 548 | // a `null` value means `key.charAt(0).toUpperCase() + key.slice(1)` 549 | const schemaChangedNames = { 550 | uniqueSymbolFromSelf: null, 551 | undefined: null, 552 | void: null, 553 | null: null, 554 | never: null, 555 | unknown: null, 556 | any: null, 557 | string: null, 558 | number: null, 559 | boolean: null, 560 | symbolFromSelf: null, 561 | object: null, 562 | union: null, 563 | nullable: "NullOr", 564 | orUndefined: "UndefinedOr", 565 | nullish: "NullishOr", 566 | tuple: null, 567 | array: null, 568 | nonEmptyArray: null, 569 | struct: null, 570 | record: null, 571 | symbol: null, 572 | optionFromSelf: null, 573 | option: null, 574 | optionFromNullable: "OptionFromNullOr", 575 | optionFromNullish: "OptionFromNullishOr", 576 | optionFromOrUndefined: "OptionFromUndefinedOr", 577 | eitherFromSelf: null, 578 | either: null, 579 | eitherFromUnion: null, 580 | readonlyMapFromSelf: null, 581 | mapFromSelf: null, 582 | readonlyMap: null, 583 | map: null, 584 | readonlySetFromSelf: null, 585 | setFromSelf: null, 586 | readonlySet: null, 587 | set: null, 588 | chunkFromSelf: null, 589 | chunk: null, 590 | dataFromSelf: null, 591 | data: null, 592 | causeFromSelf: null, 593 | causeDefectUnknown: null, 594 | cause: null, 595 | exitFromSelf: null, 596 | exit: null, 597 | hashSetFromSelf: null, 598 | hashSet: null, 599 | hashMapFromSelf: null, 600 | hashMap: null, 601 | listFromSelf: null, 602 | list: null, 603 | sortedSetFromSelf: null, 604 | sortedSet: null, 605 | literal: null, 606 | enums: null, 607 | templateLiteral: null, 608 | } 609 | 610 | const isSchemaNameChanged = ( 611 | key: string, 612 | ): key is keyof typeof schemaChangedNames => key in schemaChangedNames 613 | 614 | const typeLevelSchemaChangedNames = { 615 | literal: null, 616 | enums: null, 617 | $void: "Void", 618 | $undefined: "Undefined", 619 | $null: "Null", 620 | never: null, 621 | $unknown: "Unknown", 622 | $any: "Any", 623 | $string: "$String", 624 | $number: "$Number", 625 | $boolean: "$Boolean", 626 | symbolFromSelf: null, 627 | $object: "$Object", 628 | union: null, 629 | nullable: "NullOr", 630 | orUndefined: "UndefinedOr", 631 | nullish: "NullishOr", 632 | tupleType: null, 633 | tuple: null, 634 | array: "$Array", 635 | nonEmptyArray: null, 636 | typeLiteral: null, 637 | struct: null, 638 | record: "$Record", 639 | $symbol: "$Symbol", 640 | optionFromSelf: null, 641 | option: null, 642 | optionFromNullable: "OptionFromNullOr", 643 | optionFromNullish: "OptionFromNullishOr", 644 | optionFromOrUndefined: "OptionFromUndefinedOr", 645 | eitherFromSelf: null, 646 | either: null, 647 | eitherFromUnion: null, 648 | readonlyMapFromSelf: null, 649 | mapFromSelf: null, 650 | readonlyMap: "$ReadonlyMap", 651 | map: "$Map", 652 | readonlySetFromSelf: null, 653 | setFromSelf: null, 654 | readonlySet: "$ReadonlySet", 655 | set: "$Set", 656 | chunkFromSelf: null, 657 | chunk: null, 658 | causeFromSelf: null, 659 | cause: null, 660 | exitFromSelf: null, 661 | exit: null, 662 | hashSetFromSelf: null, 663 | hashSet: null, 664 | hashMapFromSelf: null, 665 | hashMap: null, 666 | listFromSelf: null, 667 | list: null, 668 | sortedSetFromSelf: null, 669 | sortedSet: null, 670 | } 671 | 672 | const isTypeLevelSchemaNameChanged = ( 673 | key: string, 674 | ): key is keyof typeof typeLevelSchemaChangedNames => 675 | key in typeLevelSchemaChangedNames 676 | 677 | const formatterChangedNames = { 678 | formatIssue: "formatIssueSync", 679 | formatError: "formatErrorSync", 680 | formatIssueEffect: "formatIssue", 681 | formatErrorEffect: "formatError", 682 | } 683 | 684 | const isFormatterNameChanged = ( 685 | key: string, 686 | ): key is keyof typeof formatterChangedNames => key in formatterChangedNames 687 | 688 | const astChangedNames = { 689 | isTransform: "isTransformation", 690 | } 691 | 692 | const isASTNameChanged = ( 693 | key: string, 694 | ): key is keyof typeof astChangedNames => key in astChangedNames 695 | 696 | const parseResultChangedNames = { 697 | Tuple: "TupleType", 698 | } 699 | 700 | const isParseResultNameChanged = ( 701 | key: string, 702 | ): key is keyof typeof parseResultChangedNames => key in parseResultChangedNames 703 | 704 | const arbitraryChangedNames = { 705 | make: "makeLazy", 706 | } 707 | 708 | const isArbitraryNameChanged = ( 709 | key: string, 710 | ): key is keyof typeof arbitraryChangedNames => key in arbitraryChangedNames 711 | 712 | const typeLevelArbitraryChangedNames = { 713 | Arbitrary: "LazyArbitrary", 714 | } 715 | 716 | const isTypeLevelArbitraryNameChanged = ( 717 | key: string, 718 | ): key is keyof typeof typeLevelArbitraryChangedNames => 719 | key in typeLevelArbitraryChangedNames 720 | -------------------------------------------------------------------------------- /test/schema-0.65.test.ts: -------------------------------------------------------------------------------- 1 | import * as TestUtils from "jscodeshift/src/testUtils" 2 | import { describe } from "vitest" 3 | 4 | import transformer from "../public/codemods/schema-0.65" 5 | 6 | const expectTransformation = ( 7 | description: string, 8 | input: string, 9 | output: string, 10 | ) => { 11 | TestUtils.defineInlineTest( 12 | { default: transformer, parser: "ts" }, 13 | {}, 14 | input, 15 | output, 16 | description, 17 | ) 18 | } 19 | 20 | describe("imports", () => { 21 | expectTransformation( 22 | "no imports", 23 | `import { ParseResult } from "@effect/schema" 24 | const f = ParseResult.parseError`, 25 | `import { ParseResult } from "@effect/schema" 26 | const f = ParseResult.parseError`, 27 | ) 28 | 29 | expectTransformation( 30 | "named import", 31 | `import { Schema } from "@effect/schema" 32 | const schema = Schema.string`, 33 | `import { Schema } from "@effect/schema" 34 | const schema = Schema.String`, 35 | ) 36 | 37 | expectTransformation( 38 | "named import with binding", 39 | `import { Schema as S } from "@effect/schema" 40 | const schema = S.string`, 41 | `import { Schema as S } from "@effect/schema" 42 | const schema = S.String`, 43 | ) 44 | 45 | expectTransformation( 46 | "namespace import", 47 | `import * as S from "@effect/schema/Schema" 48 | const schema = S.string`, 49 | `import * as S from "@effect/schema/Schema" 50 | const schema = S.String`, 51 | ) 52 | }) 53 | 54 | describe("consolidate schema names", () => { 55 | expectTransformation( 56 | "string", 57 | `import * as S from "@effect/schema/Schema" 58 | const schema = S.string`, 59 | `import * as S from "@effect/schema/Schema" 60 | const schema = S.String`, 61 | ) 62 | 63 | expectTransformation( 64 | "APIs with /bigint/ig", 65 | `import * as S from "@effect/schema/Schema" 66 | const schema1 = S.bigintFromSelf 67 | const schema2 = S.NonNegativeBigintFromSelf`, 68 | `import * as S from "@effect/schema/Schema" 69 | const schema1 = S.BigIntFromSelf 70 | const schema2 = S.NonNegativeBigIntFromSelf`, 71 | ) 72 | 73 | expectTransformation( 74 | "struct", 75 | `import * as S from "@effect/schema/Schema" 76 | const schema = S.struct({})`, 77 | `import * as S from "@effect/schema/Schema" 78 | const schema = S.Struct({})`, 79 | ) 80 | 81 | expectTransformation( 82 | "array", 83 | `import * as S from "@effect/schema/Schema" 84 | const schema = S.array({})`, 85 | `import * as S from "@effect/schema/Schema" 86 | const schema = S.Array({})`, 87 | ) 88 | }) 89 | 90 | describe("Schema.transform", () => { 91 | expectTransformation( 92 | "transform", 93 | `import { Schema } from "@effect/schema" 94 | 95 | const schema = Schema.transform( 96 | Schema.string, 97 | Schema.number, 98 | () => 0, 99 | () => "", 100 | )`, 101 | `import { Schema } from "@effect/schema" 102 | 103 | const schema = Schema.transform(Schema.String, Schema.Number, { 104 | decode: () => 0, 105 | encode: () => "" 106 | })`, 107 | ) 108 | 109 | expectTransformation( 110 | "transform ({ strict: false })", 111 | `import { Schema } from "@effect/schema" 112 | 113 | const schema = Schema.transform( 114 | Schema.string, 115 | Schema.number, 116 | () => 0, 117 | () => "", 118 | { strict: false }, 119 | )`, 120 | `import { Schema } from "@effect/schema" 121 | 122 | const schema = Schema.transform(Schema.String, Schema.Number, { 123 | strict: false, 124 | decode: () => 0, 125 | encode: () => "" 126 | })`, 127 | ) 128 | 129 | expectTransformation( 130 | "transform (pipeable)", 131 | `import { Schema } from "@effect/schema" 132 | 133 | const schema = Schema.string.pipe(Schema.transform( 134 | Schema.number, 135 | () => 0, 136 | () => "", 137 | ))`, 138 | `import { Schema } from "@effect/schema" 139 | 140 | const schema = Schema.String.pipe(Schema.transform(Schema.Number, { 141 | decode: () => 0, 142 | encode: () => "" 143 | }))`, 144 | ) 145 | 146 | expectTransformation( 147 | "transform (pipeable, { strict: false })", 148 | `import { Schema } from "@effect/schema" 149 | 150 | const schema = Schema.string.pipe(Schema.transform( 151 | Schema.number, 152 | () => 0, 153 | () => "", 154 | { strict: false }, 155 | ))`, 156 | `import { Schema } from "@effect/schema" 157 | 158 | const schema = Schema.String.pipe(Schema.transform(Schema.Number, { 159 | strict: false, 160 | decode: () => 0, 161 | encode: () => "" 162 | }))`, 163 | ) 164 | }) 165 | 166 | describe("Schema.transformOrFail", () => { 167 | expectTransformation( 168 | "transformOrFail", 169 | `import { ParseResult, Schema } from "@effect/schema" 170 | 171 | const schema = Schema.transformOrFail( 172 | Schema.string, 173 | Schema.number, 174 | () => ParseResult.succeed(0), 175 | () => ParseResult.succeed(""), 176 | )`, 177 | `import { ParseResult, Schema } from "@effect/schema" 178 | 179 | const schema = Schema.transformOrFail(Schema.String, Schema.Number, { 180 | decode: () => ParseResult.succeed(0), 181 | encode: () => ParseResult.succeed("") 182 | })`, 183 | ) 184 | 185 | expectTransformation( 186 | "transformOrFail ({ strict: false })", 187 | `import { ParseResult, Schema } from "@effect/schema" 188 | 189 | const schema = Schema.transformOrFail( 190 | Schema.string, 191 | Schema.number, 192 | () => ParseResult.succeed(0), 193 | () => ParseResult.succeed(""), 194 | { strict: false }, 195 | )`, 196 | `import { ParseResult, Schema } from "@effect/schema" 197 | 198 | const schema = Schema.transformOrFail(Schema.String, Schema.Number, { 199 | strict: false, 200 | decode: () => ParseResult.succeed(0), 201 | encode: () => ParseResult.succeed("") 202 | })`, 203 | ) 204 | 205 | expectTransformation( 206 | "transformOrFail (pipeable)", 207 | `import { Schema } from "@effect/schema" 208 | 209 | const schema = Schema.string.pipe(Schema.transformOrFail( 210 | Schema.number, 211 | () => ParseResult.succeed(0), 212 | () => ParseResult.succeed(""), 213 | ))`, 214 | `import { Schema } from "@effect/schema" 215 | 216 | const schema = Schema.String.pipe(Schema.transformOrFail(Schema.Number, { 217 | decode: () => ParseResult.succeed(0), 218 | encode: () => ParseResult.succeed("") 219 | }))`, 220 | ) 221 | 222 | expectTransformation( 223 | "transformOrFail (pipeable, { strict: false })", 224 | `import { Schema } from "@effect/schema" 225 | 226 | const schema = Schema.string.pipe(Schema.transformOrFail( 227 | Schema.number, 228 | () => ParseResult.succeed(0), 229 | () => ParseResult.succeed(""), 230 | { strict: false }, 231 | ))`, 232 | `import { Schema } from "@effect/schema" 233 | 234 | const schema = Schema.String.pipe(Schema.transformOrFail(Schema.Number, { 235 | strict: false, 236 | decode: () => ParseResult.succeed(0), 237 | encode: () => ParseResult.succeed("") 238 | }))`, 239 | ) 240 | }) 241 | 242 | describe("Schema.declare", () => { 243 | expectTransformation( 244 | "declare", 245 | `import { ParseResult, Schema } from "@effect/schema" 246 | 247 | export const schema1 = Schema.declare((u): u is File => u instanceof File) 248 | 249 | export const schema2 = ( 250 | value: Value, 251 | ) => { 252 | return Schema.declare( 253 | [value], 254 | value => ParseResult.decodeUnknown(value), 255 | value => ParseResult.encodeUnknown(value), 256 | ) 257 | }`, 258 | `import { ParseResult, Schema } from "@effect/schema" 259 | 260 | export const schema1 = Schema.declare((u): u is File => u instanceof File) 261 | 262 | export const schema2 = ( 263 | value: Value, 264 | ) => { 265 | return Schema.declare([value], { 266 | decode: value => ParseResult.decodeUnknown(value), 267 | encode: value => ParseResult.encodeUnknown(value) 268 | }); 269 | }`, 270 | ) 271 | }) 272 | 273 | describe("Schema.optionalToRequired", () => { 274 | expectTransformation( 275 | "optionalToRequired", 276 | `import { Schema } from "@effect/schema" 277 | import * as Option from "effect/Option" 278 | 279 | const schema = Schema.optionalToRequired( 280 | Schema.nullable(Schema.string), 281 | Schema.string, 282 | Option.match({ 283 | onNone: () => "", 284 | onSome: a => a === null ? "" : a, 285 | }), 286 | Option.some, 287 | )`, 288 | `import { Schema } from "@effect/schema" 289 | import * as Option from "effect/Option" 290 | 291 | const schema = Schema.optionalToRequired(Schema.NullOr(Schema.String), Schema.String, { 292 | decode: Option.match({ 293 | onNone: () => "", 294 | onSome: a => a === null ? "" : a, 295 | }), 296 | 297 | encode: Option.some 298 | })`, 299 | ) 300 | }) 301 | 302 | describe("Schema.optionalToOptional", () => { 303 | expectTransformation( 304 | "optionalToOptional", 305 | `import { Schema } from "@effect/schema" 306 | import * as Option from "effect/Option" 307 | import * as Predicate from "effect/Predicate" 308 | 309 | const schema = Schema.optionalToOptional( 310 | Schema.nullable(Schema.string), 311 | Schema.string, 312 | Option.filter(Predicate.isNotNull), 313 | a => a, 314 | )`, 315 | `import { Schema } from "@effect/schema" 316 | import * as Option from "effect/Option" 317 | import * as Predicate from "effect/Predicate" 318 | 319 | const schema = Schema.optionalToOptional(Schema.NullOr(Schema.String), Schema.String, { 320 | decode: Option.filter(Predicate.isNotNull), 321 | encode: a => a 322 | })`, 323 | ) 324 | }) 325 | 326 | describe("Class.transformOrFail*", () => { 327 | expectTransformation( 328 | "Class.transformOrFail*", 329 | `import { ParseResult, Schema } from "@effect/schema" 330 | 331 | class A extends Schema.Class("A")({ 332 | a: Schema.string, 333 | }) {} 334 | 335 | class B extends A.transformOrFail("B")( 336 | { b: Schema.number }, 337 | () => ParseResult.succeed({ a: "a", b: 1 }), 338 | () => ParseResult.succeed({ a: "a" }), 339 | ) {} 340 | 341 | class C extends A.transformOrFailFrom("C")( 342 | { b: Schema.number }, 343 | () => ParseResult.succeed({ a: "a", b: 1 }), 344 | () => ParseResult.succeed({ a: "a" }), 345 | ) {}`, 346 | `import { ParseResult, Schema } from "@effect/schema" 347 | 348 | class A extends Schema.Class("A")({ 349 | a: Schema.String, 350 | }) {} 351 | 352 | class B extends A.transformOrFail("B")({ b: Schema.Number }, { 353 | decode: () => ParseResult.succeed({ a: "a", b: 1 }), 354 | encode: () => ParseResult.succeed({ a: "a" }) 355 | }) {} 356 | 357 | class C extends A.transformOrFailFrom("C")({ b: Schema.Number }, { 358 | decode: () => ParseResult.succeed({ a: "a", b: 1 }), 359 | encode: () => ParseResult.succeed({ a: "a" }) 360 | }) {}`, 361 | ) 362 | }) 363 | 364 | describe("TreeFormatter", () => { 365 | expectTransformation( 366 | `import { formatError } from "@effect/schema/TreeFormatter"`, 367 | `import { formatError } from "@effect/schema/TreeFormatter" 368 | 369 | formatError(ParseResult.parseError(err))`, 370 | `import { formatErrorSync } from "@effect/schema/TreeFormatter" 371 | 372 | formatErrorSync(ParseResult.parseError(err))`, 373 | ) 374 | 375 | expectTransformation( 376 | `import { formatErrorEffect } from "@effect/schema/TreeFormatter"`, 377 | `import { formatErrorEffect } from "@effect/schema/TreeFormatter" 378 | 379 | formatErrorEffect(ParseResult.parseError(err))`, 380 | `import { formatError } from "@effect/schema/TreeFormatter" 381 | 382 | formatError(ParseResult.parseError(err))`, 383 | ) 384 | 385 | expectTransformation( 386 | `import { formatError, formatErrorEffect } from "@effect/schema/TreeFormatter"`, 387 | `import { formatError, formatErrorEffect } from "@effect/schema/TreeFormatter" 388 | 389 | formatError(ParseResult.parseError(err1)) 390 | formatErrorEffect(ParseResult.parseError(err2)) 391 | formatError(ParseResult.parseError(err3)) 392 | formatErrorEffect(ParseResult.parseError(err4))`, 393 | `import { formatErrorSync, formatError } from "@effect/schema/TreeFormatter" 394 | 395 | formatErrorSync(ParseResult.parseError(err1)) 396 | formatError(ParseResult.parseError(err2)) 397 | formatErrorSync(ParseResult.parseError(err3)) 398 | formatError(ParseResult.parseError(err4))`, 399 | ) 400 | 401 | expectTransformation( 402 | "TreeFormatter.formatIssue / TreeFormatter.formatIssueEffect", 403 | `import { ParseResult, Schema, TreeFormatter } from "@effect/schema" 404 | 405 | const message1 = TreeFormatter.formatIssueEffect( 406 | new ParseResult.Type(Schema.string.ast, null), 407 | ) 408 | 409 | const message2 = TreeFormatter.formatIssue( 410 | new ParseResult.Type(Schema.string.ast, null), 411 | )`, 412 | `import { ParseResult, Schema, TreeFormatter } from "@effect/schema" 413 | 414 | const message1 = TreeFormatter.formatIssue( 415 | new ParseResult.Type(Schema.String.ast, null), 416 | ) 417 | 418 | const message2 = TreeFormatter.formatIssueSync( 419 | new ParseResult.Type(Schema.String.ast, null), 420 | )`, 421 | ) 422 | 423 | expectTransformation( 424 | "TreeFormatter.formatError / TreeFormatter.formatErrorEffect", 425 | `import { ParseResult, Schema, TreeFormatter } from "@effect/schema" 426 | 427 | const message1 = TreeFormatter.formatErrorEffect( 428 | ParseResult.parseError(new ParseResult.Type(Schema.string.ast, null)), 429 | ) 430 | 431 | const message2 = TreeFormatter.formatError( 432 | ParseResult.parseError(new ParseResult.Type(Schema.string.ast, null)), 433 | )`, 434 | `import { ParseResult, Schema, TreeFormatter } from "@effect/schema" 435 | 436 | const message1 = TreeFormatter.formatError( 437 | ParseResult.parseError(new ParseResult.Type(Schema.String.ast, null)), 438 | ) 439 | 440 | const message2 = TreeFormatter.formatErrorSync( 441 | ParseResult.parseError(new ParseResult.Type(Schema.String.ast, null)), 442 | )`, 443 | ) 444 | }) 445 | 446 | describe("ArrayFormatter", () => { 447 | expectTransformation( 448 | `import { formatError } from "@effect/schema/ArrayFormatter"`, 449 | `import { formatError } from "@effect/schema/ArrayFormatter" 450 | 451 | formatError(ParseResult.parseError(err))`, 452 | `import { formatErrorSync } from "@effect/schema/ArrayFormatter" 453 | 454 | formatErrorSync(ParseResult.parseError(err))`, 455 | ) 456 | 457 | expectTransformation( 458 | `import { formatErrorEffect } from "@effect/schema/ArrayFormatter"`, 459 | `import { formatErrorEffect } from "@effect/schema/ArrayFormatter" 460 | 461 | formatErrorEffect(ParseResult.parseError(err))`, 462 | `import { formatError } from "@effect/schema/ArrayFormatter" 463 | 464 | formatError(ParseResult.parseError(err))`, 465 | ) 466 | 467 | expectTransformation( 468 | `import { formatError, formatErrorEffect } from "@effect/schema/ArrayFormatter"`, 469 | `import { formatError, formatErrorEffect } from "@effect/schema/ArrayFormatter" 470 | 471 | formatError(ParseResult.parseError(err1)) 472 | formatErrorEffect(ParseResult.parseError(err2)) 473 | formatError(ParseResult.parseError(err3)) 474 | formatErrorEffect(ParseResult.parseError(err4))`, 475 | `import { formatErrorSync, formatError } from "@effect/schema/ArrayFormatter" 476 | 477 | formatErrorSync(ParseResult.parseError(err1)) 478 | formatError(ParseResult.parseError(err2)) 479 | formatErrorSync(ParseResult.parseError(err3)) 480 | formatError(ParseResult.parseError(err4))`, 481 | ) 482 | 483 | expectTransformation( 484 | "ArrayFormatter.formatIssue / ArrayFormatter.formatIssueEffect", 485 | `import { ParseResult, Schema, ArrayFormatter } from "@effect/schema" 486 | 487 | const message1 = ArrayFormatter.formatIssueEffect( 488 | new ParseResult.Type(Schema.string.ast, null), 489 | ) 490 | 491 | const message2 = ArrayFormatter.formatIssue( 492 | new ParseResult.Type(Schema.string.ast, null), 493 | )`, 494 | `import { ParseResult, Schema, ArrayFormatter } from "@effect/schema" 495 | 496 | const message1 = ArrayFormatter.formatIssue( 497 | new ParseResult.Type(Schema.String.ast, null), 498 | ) 499 | 500 | const message2 = ArrayFormatter.formatIssueSync( 501 | new ParseResult.Type(Schema.String.ast, null), 502 | )`, 503 | ) 504 | 505 | expectTransformation( 506 | "ArrayFormatter.formatError / ArrayFormatter.formatErrorEffect", 507 | `import { ParseResult, Schema, ArrayFormatter } from "@effect/schema" 508 | 509 | const message1 = ArrayFormatter.formatErrorEffect( 510 | ParseResult.parseError(new ParseResult.Type(Schema.string.ast, null)), 511 | ) 512 | 513 | const message2 = ArrayFormatter.formatError( 514 | ParseResult.parseError(new ParseResult.Type(Schema.string.ast, null)), 515 | )`, 516 | `import { ParseResult, Schema, ArrayFormatter } from "@effect/schema" 517 | 518 | const message1 = ArrayFormatter.formatError( 519 | ParseResult.parseError(new ParseResult.Type(Schema.String.ast, null)), 520 | ) 521 | 522 | const message2 = ArrayFormatter.formatErrorSync( 523 | ParseResult.parseError(new ParseResult.Type(Schema.String.ast, null)), 524 | )`, 525 | ) 526 | }) 527 | 528 | describe("AST", () => { 529 | expectTransformation( 530 | "isTransform", 531 | `import { AST, Schema } from "@effect/schema" 532 | 533 | const b = AST.isTransform(Schema.string.ast)`, 534 | `import { AST, Schema } from "@effect/schema" 535 | 536 | const b = AST.isTransformation(Schema.String.ast)`, 537 | ) 538 | }) 539 | 540 | describe("ParseResult", () => { 541 | expectTransformation( 542 | "Tuple", 543 | `import { AST, ParseResult, Schema } from "@effect/schema" 544 | 545 | const issue = new ParseResult.Tuple(new AST.TupleType([], [], true), null, [ 546 | new ParseResult.Index(0, new ParseResult.Unexpected(Schema.string.ast)), 547 | ])`, 548 | `import { AST, ParseResult, Schema } from "@effect/schema" 549 | 550 | const issue = new ParseResult.TupleType(new AST.TupleType([], [], true), null, [ 551 | new ParseResult.Index(0, new ParseResult.Unexpected(Schema.String.ast)), 552 | ])`, 553 | ) 554 | }) 555 | 556 | describe.skip("change arguments", () => { 557 | expectTransformation( 558 | "EitheFromSelf", 559 | `import { Schema } from "@effect/schema" 560 | 561 | const schema = Schema.eitherFromSelf({ 562 | right: Schema.number, 563 | left: Schema.string, 564 | })`, 565 | `import { Schema } from "@effect/schema" 566 | 567 | const schema = Schema.EitherFromSelf({ 568 | Right: Schema.Number, 569 | Left: Schema.String, 570 | })`, 571 | ) 572 | 573 | expectTransformation( 574 | "Either", 575 | `import { Schema } from "@effect/schema" 576 | 577 | const schema = Schema.either({ 578 | right: Schema.number, 579 | left: Schema.string, 580 | })`, 581 | `import { Schema } from "@effect/schema" 582 | 583 | const schema = Schema.Either({ 584 | Right: Schema.Number, 585 | Left: Schema.String, 586 | })`, 587 | ) 588 | 589 | expectTransformation( 590 | "EitherFromUnion", 591 | `import { Schema } from "@effect/schema" 592 | 593 | const schema = Schema.eitherFromUnion({ 594 | right: Schema.number, 595 | left: Schema.string, 596 | })`, 597 | `import { Schema } from "@effect/schema" 598 | 599 | const schema = Schema.EitherFromUnion({ 600 | Right: Schema.Number, 601 | Left: Schema.String, 602 | })`, 603 | ) 604 | 605 | expectTransformation( 606 | "ReadonlyMapFromSelf", 607 | `import { Schema } from "@effect/schema" 608 | 609 | const schema = Schema.readonlyMapFromSelf({ 610 | value: Schema.number, 611 | key: Schema.string, 612 | })`, 613 | `import { Schema } from "@effect/schema" 614 | 615 | const schema = Schema.ReadonlyMapFromSelf({ 616 | Value: Schema.Number, 617 | Key: Schema.String, 618 | })`, 619 | ) 620 | 621 | expectTransformation( 622 | "ExitFromSelf", 623 | `import { Schema } from "@effect/schema" 624 | 625 | const schema = Schema.exitFromSelf({ 626 | success: Schema.number, 627 | failure: Schema.string, 628 | })`, 629 | `import { Schema } from "@effect/schema" 630 | 631 | const schema = Schema.ExitFromSelf({ 632 | Success: Schema.Number, 633 | Failure: Schema.String, 634 | })`, 635 | ) 636 | 637 | expectTransformation( 638 | "ExitFromSelf (with defect)", 639 | `import { Schema } from "@effect/schema" 640 | 641 | const schema = Schema.exitFromSelf({ 642 | success: Schema.number, 643 | failure: Schema.string, 644 | defect: Schema.unknown 645 | })`, 646 | `import { Schema } from "@effect/schema" 647 | 648 | const schema = Schema.ExitFromSelf({ 649 | Success: Schema.Number, 650 | Failure: Schema.String, 651 | Defect: Schema.Unknown 652 | })`, 653 | ) 654 | }) 655 | 656 | describe("Arbitrary", () => { 657 | expectTransformation( 658 | "make -> makeLazy", 659 | `import { Arbitrary, Schema } from "@effect/schema" 660 | 661 | const Person = Schema.struct({ 662 | name: Schema.string, 663 | age: Schema.string.pipe( 664 | Schema.compose(Schema.NumberFromString), 665 | Schema.int(), 666 | ), 667 | }) 668 | 669 | const arb = Arbitrary.make(Person)`, 670 | `import { Arbitrary, Schema } from "@effect/schema" 671 | 672 | const Person = Schema.Struct({ 673 | name: Schema.String, 674 | age: Schema.String.pipe( 675 | Schema.compose(Schema.NumberFromString), 676 | Schema.int(), 677 | ), 678 | }) 679 | 680 | const arb = Arbitrary.makeLazy(Person)`, 681 | ) 682 | 683 | expectTransformation( 684 | "Arbitrary.LazyArbitrary (namespace import)", 685 | `import type * as Arbitrary from "@effect/schema/Arbitrary" 686 | import * as S from "@effect/schema/Schema" 687 | 688 | const schema = S.string.annotations({ 689 | arbitrary: (): Arbitrary.Arbitrary => fc => fc.string(), 690 | })`, 691 | `import type * as Arbitrary from "@effect/schema/Arbitrary" 692 | import * as S from "@effect/schema/Schema" 693 | 694 | const schema = S.String.annotations({ 695 | arbitrary: (): Arbitrary.LazyArbitrary => fc => fc.string(), 696 | })`, 697 | ) 698 | 699 | expectTransformation( 700 | "Arbitrary.LazyArbitrary (named import)", 701 | `import { Schema } from "@effect/schema" 702 | import type { Arbitrary } from "@effect/schema" 703 | 704 | const schema = Schema.string.annotations({ 705 | arbitrary: (): Arbitrary.Arbitrary => fc => fc.string(), 706 | })`, 707 | `import { Schema } from "@effect/schema" 708 | import type { Arbitrary } from "@effect/schema" 709 | 710 | const schema = Schema.String.annotations({ 711 | arbitrary: (): Arbitrary.LazyArbitrary => fc => fc.string(), 712 | })`, 713 | ) 714 | 715 | expectTransformation( 716 | "Arbitrary.LazyArbitrary (type named import)", 717 | `import { Schema } from "@effect/schema" 718 | import type { Arbitrary } from "@effect/schema/Arbitrary" 719 | 720 | const schema = Schema.string.annotations({ 721 | arbitrary: (): Arbitrary => fc => fc.string(), 722 | })`, 723 | `import { Schema } from "@effect/schema" 724 | import type { LazyArbitrary } from "@effect/schema/Arbitrary" 725 | 726 | const schema = Schema.String.annotations({ 727 | arbitrary: (): LazyArbitrary => fc => fc.string(), 728 | })`, 729 | ) 730 | 731 | expectTransformation( 732 | "Arbitrary.LazyArbitrary (type named import with binding)", 733 | `import { Schema } from "@effect/schema" 734 | import type { Arbitrary as A } from "@effect/schema/Arbitrary" 735 | 736 | const schema = Schema.string.annotations({ 737 | arbitrary: (): A => fc => fc.string(), 738 | })`, 739 | `import { Schema } from "@effect/schema" 740 | import type { LazyArbitrary as A } from "@effect/schema/Arbitrary" 741 | 742 | const schema = Schema.String.annotations({ 743 | arbitrary: (): A => fc => fc.string(), 744 | })`, 745 | ) 746 | }) 747 | 748 | describe("type-level", () => { 749 | expectTransformation( 750 | "S.struct<>", 751 | `import type * as S from "@effect/schema/Schema" 752 | 753 | export function struct( 754 | fields: Fields, 755 | ): S.struct 756 | export function struct( 757 | fields: Fields, 758 | ): S.struct {}`, 759 | `import type * as S from "@effect/schema/Schema" 760 | 761 | export function struct( 762 | fields: Fields, 763 | ): S.Struct 764 | export function struct( 765 | fields: Fields, 766 | ): S.Struct {}`, 767 | ) 768 | 769 | expectTransformation( 770 | "null", 771 | `import type * as S from "@effect/schema/Schema" 772 | 773 | type N = S.$null`, 774 | `import type * as S from "@effect/schema/Schema" 775 | 776 | type N = S.Null`, 777 | ) 778 | }) 779 | --------------------------------------------------------------------------------