Skip to content

Commit 8f15dd5

Browse files
author
Daniel Del Core
committed
Adds hypermod codemod
1 parent 30ae578 commit 8f15dd5

File tree

5 files changed

+316
-0
lines changed

5 files changed

+316
-0
lines changed

community/hypermod/package.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "@hypermod/mod-hypermod",
3+
"version": "0.0.0",
4+
"license": "MIT",
5+
"source": "src/hypermod.config.js",
6+
"main": "dist/hypermod.config.js",
7+
"scripts": {
8+
"dev": "hypermod",
9+
"build": "parcel build",
10+
"test": "jest --watch",
11+
"validate": "hypermod validate ."
12+
},
13+
"dependencies": {
14+
"@hypermod/utils": "^0.4.2",
15+
"jscodeshift": "^0.13.1"
16+
},
17+
"devDependencies": {
18+
"@hypermod/cli": "^0.19.3",
19+
"@types/jest": "^29.0.0",
20+
"@types/node": "^16.11.0",
21+
"jest": "^29.0.0",
22+
"parcel": "^2.8.3",
23+
"prettier": "^2.0.0",
24+
"ts-jest": "^29.0.0",
25+
"typescript": "^5.2.2"
26+
},
27+
"repository": "https://github.com/hypermod-io/hypermod-community/tree/main/community/hypermod"
28+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# hypermod#defineInlineTest-to-applyTransform
2+
3+
Codemods for hypermod#defineInlineTest-to-applyTransform
4+
5+
This codemod transforms test cases written with `defineInlineTest` from ` jscodeshift`` to use the `applyTransform`function from`@hypermod/utils`. Below is an example of the transformation:
6+
7+
```js
8+
/* INPUT */
9+
const defineInlineTest = require('jscodeshift/dist/testUtils').defineInlineTest;
10+
11+
defineInlineTest(
12+
{ default: transformer, parser: 'tsx' },
13+
{},
14+
`
15+
import React from "react";
16+
import Tag from "@atlaskit/tag";
17+
export default () => <Tag text="Removable button"/>;
18+
`,
19+
`
20+
import React from "react";
21+
import Tag from "@atlaskit/tag";
22+
export default () => <Tag text="Removable button"/>;
23+
`,
24+
'should not add isRemovable flag when no removeButtonText defined',
25+
);
26+
27+
/* OUTPUT */
28+
import { applyTransform } from '@hypermod/utils';
29+
30+
it('should not add isRemovable flag when no removeButtonText defined', async () => {
31+
const result = await applyTransform(
32+
transformer,
33+
`
34+
import React from "react";
35+
import Tag from "@atlaskit/tag";
36+
export default () => <Tag text="Removable button"/>;
37+
`,
38+
{ parser: 'tsx' },
39+
);
40+
expect(result).toMatchInlineSnapshot(`
41+
"import React from "react";
42+
import Tag from "@atlaskit/tag";
43+
export default () => <Tag text="Removable button"/>;"
44+
`);
45+
});
46+
```
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { applyTransform } from '@hypermod/utils';
2+
import * as transformer from './transform';
3+
4+
describe('hypermod#defineInlineTest-to-applyTransform transform', () => {
5+
it('should transform defineInlineTest to applyTransform wrapped in it function', async () => {
6+
const inputCode = `
7+
const defineInlineTest = require('jscodeshift/dist/testUtils').defineInlineTest;
8+
defineInlineTest(
9+
{ default: transformer, parser: 'tsx' },
10+
{},
11+
'input code',
12+
'output code',
13+
'test description'
14+
);`;
15+
16+
const expectedOutput = `import { applyTransform } from "@hypermod/utils";
17+
it('test description', async () => {
18+
const result = await applyTransform(transformer, 'input code', {
19+
parser: 'tsx'
20+
});
21+
22+
expect(result).toMatchInlineSnapshot('output code');
23+
});`;
24+
25+
const result = await applyTransform(transformer, inputCode, {
26+
parser: 'tsx',
27+
});
28+
29+
expect(result).toBe(expectedOutput.trim());
30+
});
31+
32+
it('should not transform unrelated code', async () => {
33+
const inputCode = `console.log('hello world');`;
34+
const result = await applyTransform(transformer, inputCode, {
35+
parser: 'tsx',
36+
});
37+
38+
expect(result).toBe(inputCode);
39+
});
40+
41+
it('should be idempotent', async () => {
42+
const inputCode = `
43+
const defineInlineTest = require('jscodeshift/dist/testUtils').defineInlineTest;
44+
defineInlineTest(
45+
{ default: transformer, parser: 'tsx' },
46+
{},
47+
'input code',
48+
'output code',
49+
'test description'
50+
);`;
51+
52+
const expectedOutput = `import { applyTransform } from "@hypermod/utils";
53+
it('test description', async () => {
54+
const result = await applyTransform(transformer, 'input code', {
55+
parser: 'tsx'
56+
});
57+
58+
expect(result).toMatchInlineSnapshot('output code');
59+
});`;
60+
61+
const firstPass = await applyTransform(transformer, inputCode, {
62+
parser: 'tsx',
63+
});
64+
65+
expect(firstPass).toBe(expectedOutput);
66+
67+
const secondPass = await applyTransform(transformer, firstPass, {
68+
parser: 'tsx',
69+
});
70+
expect(secondPass).toBe(firstPass);
71+
});
72+
});
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import core, { API, FileInfo, Options, Collection } from 'jscodeshift';
2+
3+
import { hasImportDeclaration, hasImportSpecifier } from '@hypermod/utils';
4+
5+
export default function transformer(
6+
file: FileInfo,
7+
{ jscodeshift: j }: API,
8+
options: Options,
9+
) {
10+
const source = j(file.source);
11+
12+
if (!hasTestUtilsImport(j, source)) {
13+
return file.source;
14+
}
15+
16+
// Replace the import of defineInlineTest with applyTransform from '@hypermod/utils'
17+
replaceImport(j, source);
18+
19+
// Transform defineInlineTest calls to it function with applyTransform
20+
transformDefineInlineTestCalls(j, source);
21+
22+
return source.toSource(options);
23+
}
24+
25+
function replaceImport(j: core.JSCodeshift, source: Collection<any>) {
26+
// Remove defineInlineTest import or require
27+
source
28+
.find(j.ImportDeclaration)
29+
.filter(path => path.node.source.value === 'jscodeshift/dist/testUtils')
30+
.remove();
31+
32+
source
33+
.find(j.VariableDeclarator)
34+
.filter(path => {
35+
return (
36+
path.node.init &&
37+
// @ts-expect-error
38+
path.node.init.object &&
39+
// @ts-expect-error
40+
path.node.init.object.callee &&
41+
// @ts-expect-error
42+
path.node.init.object.callee.name === 'require' &&
43+
// @ts-expect-error
44+
path.node.init.object.arguments.length &&
45+
// @ts-expect-error
46+
path.node.init.object.arguments[0].value ===
47+
'jscodeshift/dist/testUtils'
48+
);
49+
})
50+
.remove();
51+
52+
if (hasImportSpecifier(j, source, 'applyTransform', '@hypermod/utils')) {
53+
return;
54+
}
55+
56+
// Insert the applyTransform import
57+
const applyTransformImport = j.importDeclaration(
58+
[j.importSpecifier(j.identifier('applyTransform'))],
59+
j.stringLiteral('@hypermod/utils'),
60+
);
61+
62+
// Insert the new import at the top of the file
63+
source.get().node.program.body.unshift(applyTransformImport);
64+
}
65+
66+
function hasTestUtilsImport(j: core.JSCodeshift, source: Collection<any>) {
67+
const hasRequireStatement = source.find(j.VariableDeclarator).filter(path => {
68+
return (
69+
path.node.init &&
70+
// @ts-expect-error
71+
path.node.init.object &&
72+
// @ts-expect-error
73+
path.node.init.object.callee &&
74+
// @ts-expect-error
75+
path.node.init.object.callee.name === 'require' &&
76+
// @ts-expect-error
77+
path.node.init.object.arguments.length &&
78+
// @ts-expect-error
79+
path.node.init.object.arguments[0].value === 'jscodeshift/dist/testUtils'
80+
);
81+
}).length;
82+
83+
return (
84+
hasImportSpecifier(j, source, 'applyTransform', '@hypermod/utils') ||
85+
hasRequireStatement
86+
);
87+
}
88+
89+
function transformDefineInlineTestCalls(
90+
j: core.JSCodeshift,
91+
source: Collection<any>,
92+
) {
93+
source
94+
.find(j.CallExpression)
95+
.filter(
96+
path =>
97+
// @ts-expect-error
98+
path.node.callee.name === 'defineInlineTest' ||
99+
// @ts-expect-error
100+
(path.node.callee.property &&
101+
// @ts-expect-error
102+
path.node.callee.property.name === 'defineInlineTest'),
103+
)
104+
.forEach(path => {
105+
const [transformerOpts, _configOpts, input, output, description] =
106+
path.node.arguments;
107+
108+
const transformer = j(transformerOpts).find(j.ObjectProperty, {
109+
key: { name: 'default' },
110+
});
111+
112+
const parser = j(transformerOpts).find(j.ObjectProperty, {
113+
key: { name: 'parser' },
114+
});
115+
116+
if (!parser.length || !transformer.length) {
117+
throw new Error('Parser or transformer not found');
118+
}
119+
120+
// Create the async arrow function for the test body
121+
const testBody = j.blockStatement([
122+
j.variableDeclaration('const', [
123+
j.variableDeclarator(
124+
j.identifier('result'),
125+
j.awaitExpression(
126+
j.callExpression(j.identifier('applyTransform'), [
127+
transformer.get().value.value,
128+
input,
129+
j.objectExpression([parser.get().value]),
130+
]),
131+
),
132+
),
133+
]),
134+
j.expressionStatement(
135+
j.callExpression(
136+
j.memberExpression(
137+
j.callExpression(j.identifier('expect'), [
138+
j.identifier('result'),
139+
]),
140+
j.identifier('toMatchInlineSnapshot'),
141+
),
142+
[output],
143+
),
144+
),
145+
]);
146+
147+
const asyncArrowFunction = j.arrowFunctionExpression([], testBody, true);
148+
// https://github.com/benjamn/ast-types/issues/264#issuecomment-384392685
149+
asyncArrowFunction.async = true;
150+
151+
// Create the it function call
152+
const itFunctionCall = j.callExpression(j.identifier('it'), [
153+
description,
154+
asyncArrowFunction,
155+
]);
156+
157+
// Replace the original call with the new it function call
158+
j(path).replaceWith(itFunctionCall);
159+
});
160+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = {
2+
maintainers: [],
3+
targets: [],
4+
description: 'Codemods for hypermod',
5+
transforms: {},
6+
7+
presets: {
8+
'defineInlineTest-to-applyTransform': require('./defineInlineTest-to-applyTransform/transform'),
9+
},
10+
};

0 commit comments

Comments
 (0)