From d948e6b0c1cd71860d241bf1685ca508f4518620 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sat, 28 Mar 2026 19:31:07 +0100 Subject: [PATCH] doc(contributing): specify migration naming convention & test structure --- CONTRIBUTING.md | 83 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7150f2b1..b411f17b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,13 +41,44 @@ Each codemod resides in its own directory under `recipes/` and should include: > [!NOTE] > The `workflow.ts` naming is conventional but can be changed as needed. Ensure to update the `workflow.yml` accordingly. We use `workflow` when there are one step codemods; for multi-step codemods, consider using `what-to-change.ts` or similar descriptive names. +#### Naming + +For migrations that handle a from-to, please follow that pattern. For example `import-assertions-to-attributes`. + +#### Tests + +Codemod leverages a `before` ("input") + `after` ("expected") snapshot comparison. Codemod supports 2 options: + +* 👍 [**Single-file fixtures**](https://docs.codemod.com/jssg/testing#single-file-fixtures) + ``` + tests/ + some-test-case-description/ + input.ts + expected.ts + another-test-case-description/ + input.ts + expected.ts + ``` +* 👎 [Directory snapshot fixtures](https://docs.codemod.com/jssg/testing#directory-snapshot-fixtures) + ``` + tests/ + input/ + some-test-case-description.ts + another-test-case-description.ts + expected + some-test-case-description.ts + another-test-case-description.ts + ``` + +Use the _Single-file fixtures_ option. + ### Example Files **`src/workflow.ts` example:** ```ts import { - getNodeImportCalls, - getNodeImportStatements, + getNodeImportCalls, + getNodeImportStatements, } from '@nodejs/codemod-utils/ast-grep/import-statement'; import { getNodeRequireCalls } from "@nodejs/codemod-utils/ast-grep/require-call"; import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; @@ -64,37 +95,37 @@ import type JS from "@codemod.com/jssg-types/langs/javascript"; * ... */ export default function transform(root: SgRoot): string | null { - const rootNode = root.root(); - const edits: Edit[] = []; + const rootNode = root.root(); + const edits: Edit[] = []; - const allStatementNodes = [ - ...getNodeImportStatements(root, 'api'), - ...getNodeRequireCalls(root, 'api') - ...getNodeImportCalls(root, 'api'), - ]; + const allStatementNodes = [ + ...getNodeImportStatements(root, 'api'), + ...getNodeRequireCalls(root, 'api') + ...getNodeImportCalls(root, 'api'), + ]; - // No imports or requires for 'api', skip transformation - if (!allStatementNodes.length) return null; + // No imports or requires for 'api', skip transformation + if (!allStatementNodes.length) return null; - for (const statementNode of allStatementNodes) { - const bindingPath = resolveBindingPath(statementNode, 'api.fn'); + for (const statementNode of allStatementNodes) { + const bindingPath = resolveBindingPath(statementNode, 'api.fn'); - // Find all calls to the resolved bindingPath - const callNodes = rootNode.findDescendants((node) => { - return node.isCallExpression() && - node.getChild('callee')?.getText() === bindingPath; - }); + // Find all calls to the resolved bindingPath + const callNodes = rootNode.findDescendants((node) => { + return node.isCallExpression() && + node.getChild('callee')?.getText() === bindingPath; + }); - for (const callNode of callNodes) { - // Perform transformation on callNode - // e.g., replace 'api.fn' with 'api.newFn' - edits.push(...); - } - } + for (const callNode of callNodes) { + // Perform transformation on callNode + // e.g., replace 'api.fn' with 'api.newFn' + edits.push(...); + } + } - if (!edits.length) return null; + if (!edits.length) return null; - return rootNode.commitEdits(edits); + return rootNode.commitEdits(edits); } ```