Skip to content

Commit 20a4f27

Browse files
author
Daniel Del Core
committed
core now supports transform/preset suffixes
1 parent 5416983 commit 20a4f27

File tree

8 files changed

+2510
-1202
lines changed

8 files changed

+2510
-1202
lines changed

packages/core/lib/Worker.js

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,25 @@ function prepareJscodeshift(options) {
4545
return jscodeshift.withParser(parser);
4646
}
4747

48-
function setup(transformPath, id, babel) {
48+
function getTransform(entryPath) {
49+
const transform = entryPath.split('@');
50+
if (transform[1]) return transform[1];
51+
}
52+
53+
function getPreset(entryPath) {
54+
const preset = entryPath.split('#');
55+
if (preset[1]) return preset[1];
56+
}
57+
58+
function setup(entryPath, id, babel) {
4959
if (babel === 'babel') {
5060
const presets = [];
5161
if (presetEnv) {
5262
presets.push([presetEnv.default, { targets: { node: true } }]);
5363
}
5464

5565
presets.push(
56-
/\.tsx?$/.test(transformPath)
66+
/\.tsx?$/.test(entryPath)
5767
? require('@babel/preset-typescript').default
5868
: require('@babel/preset-flow').default,
5969
);
@@ -81,7 +91,29 @@ function setup(transformPath, id, babel) {
8191
});
8292
}
8393

84-
const transformModule = require(transformPath);
94+
const transformId = getTransform(entryPath);
95+
const presetId = getPreset(entryPath);
96+
97+
let cleanEntryPath = entryPath;
98+
let transformPkg;
99+
let transformModule;
100+
101+
if (transformId) {
102+
cleanEntryPath = entryPath.split('@')[0];
103+
transformPkg = require(cleanEntryPath);
104+
transformModule = transformPkg.transforms[transformId];
105+
}
106+
107+
if (presetId) {
108+
cleanEntryPath = entryPath.split('#')[0];
109+
transformPkg = require(cleanEntryPath);
110+
transformModule = transformPkg.presets[presetId];
111+
}
112+
113+
if (!transformId && !presetId) {
114+
transformModule = require(cleanEntryPath);
115+
}
116+
85117
transform =
86118
typeof transformModule.default === 'function'
87119
? transformModule.default

packages/core/lib/Worker.spec.js

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/* eslint-disable @typescript-eslint/no-var-requires */
2+
'use strict';
3+
4+
const fs = require('fs');
5+
const mkdirp = require('mkdirp');
6+
const path = require('path');
7+
const temp = require('temp');
8+
9+
function renameFileTo(oldPath, newFilename) {
10+
const projectPath = path.dirname(oldPath);
11+
const newPath = path.join(projectPath, newFilename);
12+
mkdirp.sync(path.dirname(newPath));
13+
fs.renameSync(oldPath, newPath);
14+
return newPath;
15+
}
16+
17+
function createTempFileWith(content, filename, extension) {
18+
const info = temp.openSync({ suffix: extension });
19+
let filePath = info.path;
20+
fs.writeSync(info.fd, content);
21+
fs.closeSync(info.fd);
22+
if (filename) {
23+
filePath = renameFileTo(filePath, filename);
24+
}
25+
return filePath;
26+
}
27+
28+
// Test transform files need a js extension to work with @babel/register
29+
// .ts or .tsx work as well
30+
function createTransformWith(content, ext = '.js') {
31+
return createTempFileWith(
32+
'module.exports = function(fileInfo, api, options) { ' + content + ' }',
33+
undefined,
34+
ext,
35+
);
36+
}
37+
38+
function getFileContent(filePath) {
39+
return fs.readFileSync(filePath).toString();
40+
}
41+
42+
describe('Worker API', () => {
43+
it('transforms files', done => {
44+
const worker = require('./Worker');
45+
const transformPath = createTransformWith(
46+
'return fileInfo.source + " changed";',
47+
);
48+
const sourcePath = createTempFileWith('foo');
49+
const emitter = worker([transformPath]);
50+
51+
emitter.send({ files: [sourcePath] });
52+
emitter.once('message', data => {
53+
expect(data.status).toBe('ok');
54+
expect(data.msg).toBe(sourcePath);
55+
expect(getFileContent(sourcePath)).toBe('foo changed');
56+
done();
57+
});
58+
});
59+
60+
it('transforms files with tranformId as extension', done => {
61+
const worker = require('./Worker');
62+
const configPath = createTempFileWith(
63+
`
64+
const transfomer = (fileInfo) => fileInfo.source + " changed";
65+
module.exports = { transforms: { "1.0.0": transfomer } };
66+
`,
67+
'codeshift1.config.js',
68+
'.js',
69+
);
70+
const sourcePath = createTempFileWith('foo');
71+
const emitter = worker([configPath + '@1.0.0']);
72+
73+
emitter.send({ files: [sourcePath] });
74+
emitter.once('message', data => {
75+
expect(data.status).toBe('ok');
76+
expect(data.msg).toBe(sourcePath);
77+
expect(getFileContent(sourcePath)).toBe('foo changed');
78+
done();
79+
});
80+
});
81+
82+
it('transforms files with presetId as extension', done => {
83+
const worker = require('./Worker');
84+
const configPath = createTempFileWith(
85+
`
86+
const transfomer = (fileInfo) => fileInfo.source + " changed";
87+
module.exports = { presets: { "my-preset": transfomer } };
88+
`,
89+
'codeshift2.config.js',
90+
'.js',
91+
);
92+
const sourcePath = createTempFileWith('foo');
93+
const emitter = worker([configPath + '#my-preset']);
94+
95+
emitter.send({ files: [sourcePath] });
96+
emitter.once('message', data => {
97+
expect(data.status).toBe('ok');
98+
expect(data.msg).toBe(sourcePath);
99+
expect(getFileContent(sourcePath)).toBe('foo changed');
100+
done();
101+
});
102+
});
103+
104+
it('passes j as argument', done => {
105+
const worker = require('./Worker');
106+
const transformPath = createTempFileWith(
107+
`module.exports = function (file, api) {
108+
return api.j(file.source).toSource() + ' changed';
109+
}`,
110+
);
111+
const sourcePath = createTempFileWith('const x = 10;');
112+
113+
const emitter = worker([transformPath]);
114+
emitter.send({ files: [sourcePath] });
115+
emitter.once('message', data => {
116+
expect(data.status).toBe('ok');
117+
expect(getFileContent(sourcePath)).toBe('const x = 10;' + ' changed');
118+
done();
119+
});
120+
});
121+
122+
describe('custom parser', () => {
123+
function getTransformForParser(parser) {
124+
return createTempFileWith(
125+
`function transform(fileInfo, api) {
126+
api.jscodeshift(fileInfo.source);
127+
return "changed";
128+
}
129+
${parser ? `transform.parser = '${parser}';` : ''}
130+
module.exports = transform;
131+
`,
132+
);
133+
}
134+
function getSourceFile() {
135+
// This code cannot be parsed by Babel v5
136+
return createTempFileWith('const x = (a: Object, b: string): void => {}');
137+
}
138+
139+
it('errors if new flow type code is parsed with babel v5', done => {
140+
const worker = require('./Worker');
141+
const transformPath = createTransformWith(
142+
'api.jscodeshift(fileInfo.source); return "changed";',
143+
);
144+
const sourcePath = getSourceFile();
145+
const emitter = worker([transformPath]);
146+
147+
emitter.send({ files: [sourcePath] });
148+
emitter.once('message', data => {
149+
expect(data.status).toBe('error');
150+
expect(data.msg).toMatch('SyntaxError');
151+
done();
152+
});
153+
});
154+
155+
['flow', 'babylon'].forEach(parser => {
156+
it(`uses ${parser} if configured as such`, done => {
157+
const worker = require('./Worker');
158+
const transformPath = getTransformForParser(parser);
159+
const sourcePath = getSourceFile();
160+
const emitter = worker([transformPath]);
161+
162+
emitter.send({ files: [sourcePath] });
163+
emitter.once('message', data => {
164+
expect(data.status).toBe('ok');
165+
expect(getFileContent(sourcePath)).toBe('changed');
166+
done();
167+
});
168+
});
169+
});
170+
171+
['babylon', 'flow', 'tsx'].forEach(parser => {
172+
it(`can parse JSX with ${parser}`, done => {
173+
const worker = require('./Worker');
174+
const transformPath = getTransformForParser(parser);
175+
const sourcePath = createTempFileWith(
176+
'var component = <div>{foobar}</div>;',
177+
);
178+
const emitter = worker([transformPath]);
179+
180+
emitter.send({ files: [sourcePath] });
181+
emitter.once('message', data => {
182+
expect(data.status).toBe('ok');
183+
expect(getFileContent(sourcePath)).toBe('changed');
184+
done();
185+
});
186+
});
187+
});
188+
189+
it('can parse enums with flow', done => {
190+
const worker = require('./Worker');
191+
const transformPath = getTransformForParser('flow');
192+
const sourcePath = createTempFileWith('enum E {A, B}');
193+
const emitter = worker([transformPath]);
194+
195+
emitter.send({ files: [sourcePath] });
196+
emitter.once('message', data => {
197+
expect(data.status).toBe('ok');
198+
expect(getFileContent(sourcePath)).toBe('changed');
199+
done();
200+
});
201+
});
202+
});
203+
});

packages/core/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"write-file-atomic": "^2.3.0"
2727
},
2828
"devDependencies": {
29-
"@types/jscodeshift": "^0.11.6"
29+
"@types/jscodeshift": "^0.11.6",
30+
"temp": "^0.8.4"
3031
}
3132
}

packages/validator/src/index.spec.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,11 @@ describe('validator', () => {
109109

110110
it('should validate config', async () => {
111111
(fetchConfig as jest.Mock).mockResolvedValue({
112-
transforms: {
113-
'10.0.0': 'path/to/transform.js',
112+
location: 'path/to/codeshift.config.js',
113+
config: {
114+
transforms: {
115+
'10.0.0': jest.fn(),
116+
},
114117
},
115118
});
116119

@@ -120,7 +123,10 @@ describe('validator', () => {
120123

121124
it('should error if config contains an invalid property', async () => {
122125
(fetchConfig as jest.Mock).mockResolvedValue({
123-
invalidProperty: 'foo',
126+
location: 'path/to/codeshift.config.js',
127+
config: {
128+
invalidProperty: 'foo',
129+
},
124130
});
125131

126132
await expect(isValidConfigAtPath('path/to/')).rejects.toThrowError(
@@ -130,9 +136,12 @@ describe('validator', () => {
130136

131137
it('should error if config contains multiple invalid properties', async () => {
132138
(fetchConfig as jest.Mock).mockResolvedValue({
133-
invalidProperty: 'foo',
134-
invalidProperty2: 'foo',
135-
invalidProperty3: 'foo',
139+
location: 'path/to/codeshift.config.js',
140+
config: {
141+
invalidProperty: 'foo',
142+
invalidProperty2: 'foo',
143+
invalidProperty3: 'foo',
144+
},
136145
});
137146

138147
await expect(isValidConfigAtPath('path/to/')).rejects.toThrowError(
@@ -142,8 +151,11 @@ describe('validator', () => {
142151

143152
it('should error if config contains invalid transforms', async () => {
144153
(fetchConfig as jest.Mock).mockResolvedValue({
145-
transforms: {
146-
hello: '',
154+
location: 'path/to/codeshift.config.js',
155+
config: {
156+
transforms: {
157+
hello: '',
158+
},
147159
},
148160
});
149161

@@ -155,8 +167,11 @@ Please make sure all transforms are identified by a valid semver version. ie 10.
155167

156168
it('should error if config contains invalid presets', async () => {
157169
(fetchConfig as jest.Mock).mockResolvedValue({
158-
presets: {
159-
'foo bar': '',
170+
location: 'path/to/codeshift.config.js',
171+
config: {
172+
presets: {
173+
'foo bar': '',
174+
},
160175
},
161176
});
162177

website/docusaurus.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,12 @@ module.exports = {
145145
'@docusaurus/preset-classic',
146146
{
147147
docs: {
148-
sidebarPath: require('./sidebars.js'),
148+
sidebarPath: require.resolve('./sidebars.js'),
149149
editUrl:
150150
'https://github.com/CodeshiftCommunity/CodeshiftCommunity/edit/main/website/',
151151
},
152152
theme: {
153-
customCss: require('./src/css/custom.css'),
153+
customCss: require.resolve('./src/css/custom.css'),
154154
},
155155
gtag: {
156156
trackingID: 'G-X9RMY7JDM0',

website/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
"write-heading-ids": "docusaurus write-heading-ids"
1515
},
1616
"dependencies": {
17-
"@docusaurus/core": "2.0.0-beta.18",
18-
"@docusaurus/preset-classic": "2.0.0-beta.18",
17+
"@docusaurus/core": "2.4.1",
18+
"@docusaurus/preset-classic": "2.4.1",
1919
"@mdx-js/react": "^1.6.21",
2020
"clsx": "^1.1.1",
2121
"react": "^17.0.1",

website/src/css/custom.css

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* stylelint-disable docusaurus/copyright-header */
21
/**
32
* Any CSS included here will be global. The classic template
43
* bundles Infima by default. Infima is a CSS framework designed to
@@ -14,7 +13,6 @@
1413
--ifm-color-primary-light: #9c6eec;
1514
--ifm-color-primary-lighter: #ad89ec;
1615
--ifm-color-primary-lightest: #c0a4f1;
17-
/* --ifm-code-font-size: 95%; */
1816
}
1917

2018
.docusaurus-highlight-code-line {

0 commit comments

Comments
 (0)