Skip to content

Commit 641d544

Browse files
max-programmingavivkellerAugustinMauroybrunocrohJakobJingleheimer
authored
feat(crypto-fips): add migration recipe for crypto.fips (#177)
Co-authored-by: Aviv Keller <[email protected]> Co-authored-by: Augustin Mauroy <[email protected]> Co-authored-by: Bruno Rodrigues <[email protected]> Co-authored-by: Jacob Smith <[email protected]>
1 parent 6556363 commit 641d544

38 files changed

+1279
-490
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@
88
"[markdown]": {
99
"editor.wordWrap": "off"
1010
},
11+
"[typescript][javascript]": {
12+
"editor.defaultFormatter": "biomejs.biome"
13+
},
1114
"typescript.experimental.useTsgo": true
1215
}

package-lock.json

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

recipes/create-require-from-path/src/workflow.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ import type JS from "@codemod.com/jssg-types/langs/javascript";
99
*
1010
* Handles:
1111
* 1. Updates import/require statements that import `createRequireFromPath`:
12-
* - `const { createRequireFromPath } = require('module')` -> `const { createRequire } = require('module')`
13-
* - `const { createRequireFromPath } = require('node:module')` -> `const { createRequire } = require('node:module')`
14-
* - `import { createRequireFromPath } from 'module'` -> `import { createRequire } from 'module'`
15-
* - `import { createRequireFromPath } from 'node:module'` -> `import { createRequire } from 'node:module'`
12+
* - `const { createRequireFromPath } = require('module')` `const { createRequire } = require('module')`
13+
* - `const { createRequireFromPath } = require('node:module')` `const { createRequire } = require('node:module')`
14+
* - `import { createRequireFromPath } from 'module'` `import { createRequire } from 'module'`
15+
* - `import { createRequireFromPath } from 'node:module'` `import { createRequire } from 'node:module'`
1616
*
1717
* 2. Updates variable declarations that use `createRequireFromPath`:
18-
* - `const myRequire = createRequireFromPath(arg)` -> `const myRequire = createRequire(arg)`
19-
* - `let myRequire = createRequireFromPath(arg)` -> `let myRequire = createRequire(arg)`
20-
* - `var myRequire = createRequireFromPath(arg)` -> `var myRequire = createRequire(arg)`
18+
* - `const myRequire = createRequireFromPath(arg)` `const myRequire = createRequire(arg)`
19+
* - `let myRequire = createRequireFromPath(arg)` `let myRequire = createRequire(arg)`
20+
* - `var myRequire = createRequireFromPath(arg)` `var myRequire = createRequire(arg)`
2121
*
2222
* 3. Preserves original variable names and declaration types.
2323
*/
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# `crypto.fips` DEP0093
2+
3+
This recipe transforms the usage from the deprecated `crypto.fips` to `crypto.getFips()` and `crypto.setFips()`.
4+
5+
See [DEP0093](https://nodejs.org/api/deprecations.html#DEP0093).
6+
7+
## Examples
8+
9+
**Before:**
10+
11+
```js
12+
import crypto from "node:crypto";
13+
import { fips } from "node:crypto";
14+
15+
// Using crypto.fips
16+
crypto.fips;
17+
fips;
18+
19+
// Using crypto.fips = true
20+
crypto.fips = true;
21+
fips = true;
22+
23+
// Using crypto.fips = false
24+
crypto.fips = false;
25+
fips = false;
26+
```
27+
28+
**After:**
29+
30+
```js
31+
import crypto from "node:crypto";
32+
import { getFips, setFips } from "node:crypto";
33+
34+
// Using crypto.getFips()
35+
crypto.getFips();
36+
getFips();
37+
38+
// Using crypto.setFips(true)
39+
crypto.setFips(true);
40+
setFips(true);
41+
42+
// Using crypto.setFips(false)
43+
crypto.setFips(false);
44+
setFips(false);
45+
```
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
schema_version: "1.0"
2+
name: "@nodejs/crypto-fips-to-getFips"
3+
version: 1.0.0
4+
description: Handle DEP0093 via transforming `crypto.fips` to `crypto.getFips()` and `crypto.setFips()`
5+
author: Usman S.
6+
license: MIT
7+
workflow: workflow.yaml
8+
category: migration
9+
10+
targets:
11+
languages:
12+
- javascript
13+
- typescript
14+
15+
keywords:
16+
- transformation
17+
- migration
18+
19+
registry:
20+
access: public
21+
visibility: public
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "@nodejs/crypto-fips-to-getFips",
3+
"version": "1.0.0",
4+
"description": "Handle DEP0093 via transforming `crypto.fips` to `crypto.getFips()` and `crypto.setFips()`",
5+
"type": "module",
6+
"scripts": {
7+
"test": "npx codemod jssg test -l typescript ./src/workflow.ts ./"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/nodejs/userland-migrations.git",
12+
"directory": "recipes/crypto-fips-to-getFips",
13+
"bugs": "https://github.com/nodejs/userland-migrations/issues"
14+
},
15+
"author": "Usman S.",
16+
"license": "MIT",
17+
"homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/crypto-fips-to-getFips/README.md",
18+
"devDependencies": {
19+
"@codemod.com/jssg-types": "^1.0.9"
20+
},
21+
"dependencies": {
22+
"@nodejs/codemod-utils": "*"
23+
}
24+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import {
2+
getNodeImportCalls,
3+
getNodeImportStatements,
4+
} from "@nodejs/codemod-utils/ast-grep/import-statement";
5+
import { getNodeRequireCalls } from "@nodejs/codemod-utils/ast-grep/require-call";
6+
import { resolveBindingPath } from "@nodejs/codemod-utils/ast-grep/resolve-binding-path";
7+
import { updateBinding } from "@nodejs/codemod-utils/ast-grep/update-binding";
8+
import { removeLines } from "@nodejs/codemod-utils/ast-grep/remove-lines";
9+
import type { SgRoot, SgNode, Edit, Range } from "@codemod.com/jssg-types/main";
10+
import type Js from "@codemod.com/jssg-types/langs/javascript";
11+
12+
type Binding = {
13+
type: "namespace" | "destructured";
14+
binding: string;
15+
node: SgNode<Js>;
16+
};
17+
18+
/**
19+
* Transform function that converts deprecated crypto.fips calls
20+
* to the new crypto.getFips() and crypto.setFips() syntax.
21+
*
22+
* Handles:
23+
* 1. crypto.fips → crypto.getFips()
24+
* 2. crypto.fips = value → crypto.setFips(value)
25+
* 3. const { fips } = require("crypto") → const { getFips, setFips } = require("crypto")
26+
* 4. import { fips } from "crypto" → import { getFips, setFips } from "crypto")
27+
* 5. const { fips } = await import("crypto") → const { getFips, setFips } = await import("crypto")
28+
* 6. Aliased imports: { fips: alias } → { getFips, setFips }
29+
*/
30+
export default function transform(root: SgRoot<Js>): string | null {
31+
const rootNode = root.root();
32+
const edits: Edit[] = [];
33+
const linesToRemove: Range[] = [];
34+
35+
const bindings = collectCryptoFipsBindings(root);
36+
37+
if (bindings.length === 0) return null;
38+
39+
for (const binding of bindings) {
40+
if (binding.type === "namespace") {
41+
edits.push(...transformNamespaceUsage(rootNode, binding.binding));
42+
} else {
43+
edits.push(...transformDestructuredUsage(rootNode, binding.binding));
44+
45+
const result = updateBinding(binding.node, {
46+
old: binding.binding,
47+
new: ["getFips", "setFips"],
48+
});
49+
if (result?.edit) {
50+
edits.push(result.edit);
51+
}
52+
if (result?.lineToRemove) {
53+
linesToRemove.push(result.lineToRemove);
54+
}
55+
}
56+
}
57+
58+
if (edits.length === 0 && linesToRemove.length === 0) return null;
59+
60+
const sourceCode = rootNode.commitEdits(edits);
61+
return linesToRemove.length > 0 ? removeLines(sourceCode, linesToRemove) : sourceCode;
62+
}
63+
64+
/**
65+
* Collect all crypto.fips bindings from the file
66+
*/
67+
function collectCryptoFipsBindings(root: SgRoot<Js>): Binding[] {
68+
const bindings: Binding[] = [];
69+
const allStatements = [
70+
...getNodeImportStatements(root, "crypto"),
71+
...getNodeImportCalls(root, "crypto"),
72+
...getNodeRequireCalls(root, "crypto"),
73+
];
74+
75+
for (const node of allStatements) {
76+
const resolvedPath = resolveBindingPath(node, "$.fips");
77+
78+
if (!resolvedPath) continue;
79+
80+
if (resolvedPath.includes(".")) {
81+
bindings.push({
82+
type: "namespace",
83+
binding: resolvedPath.slice(0, resolvedPath.lastIndexOf(".")),
84+
node,
85+
});
86+
} else {
87+
bindings.push({
88+
type: "destructured",
89+
binding: resolvedPath,
90+
node,
91+
});
92+
}
93+
}
94+
95+
return bindings;
96+
}
97+
98+
/**
99+
* Transform namespace usage: crypto.fips → crypto.getFips(), crypto.fips = val → crypto.setFips(val)
100+
*/
101+
function transformNamespaceUsage(rootNode: SgNode<Js>, base: string): Edit[] {
102+
const edits: Edit[] = [];
103+
104+
const assignments = rootNode.findAll({
105+
rule: { pattern: `${base}.fips = $VALUE` },
106+
});
107+
108+
for (const assignment of assignments) {
109+
const valueNode = assignment.getMatch("VALUE");
110+
if (valueNode) {
111+
let value = valueNode.text();
112+
value = value.replace(new RegExp(`\\b${base}\\.fips\\b`, "g"), `${base}.getFips()`);
113+
edits.push(assignment.replace(`${base}.setFips(${value})`));
114+
}
115+
}
116+
117+
const reads = rootNode.findAll({
118+
rule: {
119+
pattern: `${base}.fips`,
120+
not: {
121+
inside: {
122+
kind: "assignment_expression",
123+
has: {
124+
kind: "member_expression",
125+
pattern: `${base}.fips`,
126+
},
127+
},
128+
},
129+
},
130+
});
131+
132+
for (const read of reads) {
133+
edits.push(read.replace(`${base}.getFips()`));
134+
}
135+
136+
return edits;
137+
}
138+
139+
/**
140+
* Transform destructured usage: fips → getFips(), fips = val → setFips(val)
141+
*/
142+
function transformDestructuredUsage(rootNode: SgNode<Js>, binding: string): Edit[] {
143+
const edits: Edit[] = [];
144+
145+
const assignments = rootNode.findAll({
146+
rule: {
147+
pattern: `${binding} = $VALUE`,
148+
},
149+
});
150+
151+
for (const assignment of assignments) {
152+
const valueNode = assignment.getMatch("VALUE");
153+
if (valueNode) {
154+
let value = valueNode.text();
155+
value = value.replace(new RegExp(`\\b${binding}\\b`, "g"), "getFips()");
156+
edits.push(assignment.replace(`setFips(${value})`));
157+
}
158+
}
159+
160+
const reads = rootNode.findAll({
161+
rule: {
162+
kind: "identifier",
163+
pattern: binding,
164+
not: {
165+
inside: {
166+
any: [
167+
{
168+
kind: "assignment_expression",
169+
has: {
170+
kind: "identifier",
171+
pattern: binding,
172+
},
173+
},
174+
{ kind: "import_statement" },
175+
{ kind: "import_specifier" },
176+
{ kind: "named_imports" },
177+
{ kind: "object_pattern" },
178+
{ kind: "pair_pattern" },
179+
],
180+
},
181+
},
182+
},
183+
});
184+
185+
for (const read of reads) {
186+
edits.push(read.replace("getFips()"));
187+
}
188+
189+
return edits;
190+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const crypto = require("node:crypto");
2+
3+
if (crypto.getFips()) {
4+
console.log("FIPS mode is enabled");
5+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { getFips, setFips } from "node:crypto";
2+
3+
if (getFips()) {
4+
console.log("FIPS mode is enabled");
5+
}
6+
setFips(true);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const { getFips, setFips } = await import("node:crypto");
2+
3+
setFips(true);
4+
console.log(getFips());

0 commit comments

Comments
 (0)