Skip to content

Commit a6d2e57

Browse files
Merge pull request #46 from CodeshiftCommunity/presets
Presets
2 parents 35cc264 + b41afbb commit a6d2e57

File tree

44 files changed

+433
-81
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+433
-81
lines changed

.changeset/dull-experts-lie.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@codeshift/cli': minor
3+
---
4+
5+
CLI now supports the concept of presets, for cases where non-versioned codemods are necessary. Also exposes the cli binary as @codeshift/cli

community/@emotion__monorepo/codeshift.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@ export default {
33
transforms: {
44
'11.0.0': require.resolve('./11.0.0/transform'),
55
},
6+
presets: {
7+
'styled-to-emotion-10': require.resolve('./styled-to-emotion-10/transform'),
8+
},
69
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
jest.autoMockOff();
2+
3+
const defineInlineTest = require('jscodeshift/dist/testUtils').defineInlineTest;
4+
5+
import transformer from './transform';
6+
7+
describe('Transform import', () => {
8+
defineInlineTest(
9+
{ default: transformer, parser: 'tsx' },
10+
{},
11+
"import styled from 'styled-components';",
12+
"import styled from '@emotion/styled';",
13+
'it transforms standard styled-component imports',
14+
);
15+
16+
defineInlineTest(
17+
{ default: transformer, parser: 'tsx' },
18+
{},
19+
`
20+
import styled from 'styled-components';
21+
import react from 'react';
22+
`,
23+
`
24+
import styled from '@emotion/styled';
25+
import react from 'react';
26+
`,
27+
'it ignores other imports',
28+
);
29+
30+
defineInlineTest(
31+
{ default: transformer, parser: 'tsx' },
32+
{},
33+
"import { keyframes } from 'styled-components';",
34+
"import { keyframes } from '@emotion/core';",
35+
'it correctly detects misc core imports',
36+
);
37+
38+
defineInlineTest(
39+
{ default: transformer, parser: 'tsx' },
40+
{},
41+
"import styled, { css } from 'styled-components';",
42+
"import { css } from '@emotion/core';\nimport styled from '@emotion/styled';",
43+
'it correctly splits out core and styled imports',
44+
);
45+
46+
defineInlineTest(
47+
{ default: transformer, parser: 'tsx' },
48+
{},
49+
"import styled, { ThemeProvider } from 'styled-components';",
50+
`
51+
import styled from '@emotion/styled';
52+
import { ThemeProvider } from 'emotion-theming';
53+
`,
54+
'it correctly splits out core and themed imports',
55+
);
56+
57+
defineInlineTest(
58+
{ default: transformer, parser: 'tsx' },
59+
{},
60+
"import styled, { css, ThemeProvider, withTheme } from 'styled-components';",
61+
`
62+
import { css } from '@emotion/core';
63+
import styled from '@emotion/styled';
64+
import { ThemeProvider, withTheme } from 'emotion-theming';
65+
`,
66+
'it correctly splits out core and multiple themed imports',
67+
);
68+
});
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import core, {
2+
FileInfo,
3+
API,
4+
ASTPath,
5+
ImportDeclaration,
6+
ImportSpecifier,
7+
Options,
8+
} from 'jscodeshift';
9+
10+
function buildCoreImportDeclaration(
11+
j: core.JSCodeshift,
12+
path: ASTPath<ImportDeclaration>,
13+
) {
14+
const coreExports = ['css', 'keyframes'];
15+
const specifiers: ImportSpecifier[] = [];
16+
17+
j(path)
18+
.find(j.ImportSpecifier)
19+
.filter((specifier: any) =>
20+
coreExports.includes(specifier.value.imported.name),
21+
)
22+
.forEach((specifier: any) => {
23+
specifiers.push(
24+
j.importSpecifier(j.identifier(specifier.value.imported.name)),
25+
);
26+
});
27+
28+
return specifiers.length
29+
? j.importDeclaration(specifiers, j.literal('@emotion/core'))
30+
: null;
31+
}
32+
33+
function buildStyledImportDeclaration(
34+
j: core.JSCodeshift,
35+
path: ASTPath<ImportDeclaration>,
36+
) {
37+
const specifier = j(path)
38+
.find(j.ImportDefaultSpecifier)
39+
.filter((specifier: any) => specifier.value.local!.name === 'styled');
40+
41+
return specifier && specifier.length
42+
? j.importDeclaration(
43+
[
44+
j.importDefaultSpecifier(
45+
j.identifier(specifier.get(0).node.local!.name),
46+
),
47+
],
48+
j.literal('@emotion/styled'),
49+
)
50+
: null;
51+
}
52+
53+
function buildThemingImportDeclaration(
54+
j: core.JSCodeshift,
55+
path: ASTPath<ImportDeclaration>,
56+
) {
57+
const themingExports = ['ThemeProvider', 'withTheme'];
58+
const specifiers: ImportSpecifier[] = [];
59+
60+
j(path)
61+
.find(j.ImportSpecifier)
62+
.filter((specifier: any) =>
63+
themingExports.includes(specifier.value.imported.name),
64+
)
65+
.forEach((specifier: any) => {
66+
specifiers.push(
67+
j.importSpecifier(j.identifier(specifier.value.imported.name)),
68+
);
69+
});
70+
71+
return specifiers && specifiers.length
72+
? j.importDeclaration(specifiers, j.literal('emotion-theming'))
73+
: null;
74+
}
75+
76+
/**
77+
* Converts all imports of `styled-components` to `@emotion/styled`
78+
*/
79+
export default function transformer(
80+
fileInfo: FileInfo,
81+
{ jscodeshift: j }: API,
82+
options: Options,
83+
) {
84+
const source = j(fileInfo.source)
85+
.find(j.ImportDeclaration)
86+
.filter((path: any) => path.node.source.value === 'styled-components')
87+
.forEach((path: any) => {
88+
j(path).replaceWith([
89+
buildCoreImportDeclaration(j, path),
90+
buildStyledImportDeclaration(j, path),
91+
buildThemingImportDeclaration(j, path),
92+
]);
93+
})
94+
.toSource(options.printOptions || { quote: 'single' });
95+
96+
return source;
97+
}

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"license": "MIT",
88
"repository": "https://github.com/CodeshiftCommunity/CodeshiftCommunity/tree/master/packages/cli",
99
"bin": {
10+
"@codeshift/cli": "./bin/codeshift-cli.js",
1011
"codeshift-cli": "./bin/codeshift-cli.js"
1112
},
1213
"scripts": {

packages/cli/src/main.spec.ts

Lines changed: 130 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,16 @@ jest.mock('live-plugin-manager', () => ({
1212
'19.0.0': `${codemodName}/path/to/19.js`,
1313
'20.0.0': `${codemodName}/path/to/20.js`,
1414
},
15+
presets: {
16+
'update-formatting': `${codemodName}/path/to/update-formatting.js`,
17+
'update-imports': `${codemodName}/path/to/update-imports.js`,
18+
},
1519
},
1620
}),
1721
uninstallAll: () => Promise.resolve(),
1822
}),
1923
}));
2024

21-
// jest.mock('fs-extra', () => ({
22-
// readdir: () =>
23-
// Promise.resolve([
24-
// '18.0.0',
25-
// '19.0.0',
26-
// '20.0.0',
27-
// 'codeshift.config.js',
28-
// 'index.ts',
29-
// ]),
30-
// }));
31-
3225
// @ts-ignore
3326
import * as jscodeshift from 'jscodeshift/src/Runner';
3427

@@ -242,6 +235,25 @@ describe('main', () => {
242235
expect.any(Object),
243236
);
244237
});
238+
it('should run multiple transforms of the same package', async () => {
239+
await main([mockPath], {
240+
packages: '@myscope/[email protected]@19.0.0',
241+
parser: 'babel',
242+
extensions: 'js',
243+
});
244+
245+
expect(jscodeshift.run).toHaveBeenCalledTimes(2);
246+
expect(jscodeshift.run).toHaveBeenCalledWith(
247+
'@codeshift/mod-myscope__mylib/path/to/19.js',
248+
expect.any(Array),
249+
expect.any(Object),
250+
);
251+
expect(jscodeshift.run).toHaveBeenCalledWith(
252+
'@codeshift/mod-myscope__mylib/path/to/20.js',
253+
expect.any(Array),
254+
expect.any(Object),
255+
);
256+
});
245257

246258
it('should handle empty package transforms', async () => {
247259
await main([mockPath], {
@@ -270,7 +282,24 @@ describe('main', () => {
270282
} catch (error) {
271283
// @ts-ignore
272284
expect(error.message).toMatch(
273-
'Invalid version provided to the --packages flag. Package mylib@NOT_SEMVER is missing version. Please try: "@[scope]/[package]@[version]" for example @mylib/[email protected]',
285+
'Invalid version provided to the --packages flag. Unable to resolve version "NOT_SEMVER" for package "mylib". Please try: "[scope]/[package]@[version]" for example @mylib/[email protected]',
286+
);
287+
}
288+
});
289+
290+
it('should throw when transform is not found', async () => {
291+
expect.assertions(1);
292+
293+
try {
294+
await main([mockPath], {
295+
packages: '[email protected]',
296+
parser: 'babel',
297+
extensions: 'js',
298+
});
299+
} catch (error) {
300+
// @ts-ignore
301+
expect(error.message).toMatch(
302+
'Invalid version provided to the --packages flag. Unable to resolve version "120.0.0" for package "mylib"',
274303
);
275304
}
276305
});
@@ -299,4 +328,93 @@ describe('main', () => {
299328
);
300329
});
301330
});
331+
332+
describe('when running presets with the -p flag', () => {
333+
it('should run single preset', async () => {
334+
await main([mockPath], {
335+
packages: 'mylib#update-formatting',
336+
parser: 'babel',
337+
extensions: 'js',
338+
});
339+
340+
expect(jscodeshift.run).toHaveBeenCalledTimes(1);
341+
expect(jscodeshift.run).toHaveBeenCalledWith(
342+
'@codeshift/mod-mylib/path/to/update-formatting.js',
343+
expect.arrayContaining([mockPath]),
344+
expect.objectContaining({
345+
parser: 'babel',
346+
extensions: 'js',
347+
}),
348+
);
349+
});
350+
351+
it('should run multiple presets', async () => {
352+
await main([mockPath], {
353+
packages: 'mylib#update-formatting,mylib#update-imports',
354+
parser: 'babel',
355+
extensions: 'js',
356+
});
357+
358+
expect(jscodeshift.run).toHaveBeenCalledTimes(2);
359+
expect(jscodeshift.run).toHaveBeenCalledWith(
360+
'@codeshift/mod-mylib/path/to/update-formatting.js',
361+
expect.arrayContaining([mockPath]),
362+
expect.objectContaining({
363+
parser: 'babel',
364+
extensions: 'js',
365+
}),
366+
);
367+
expect(jscodeshift.run).toHaveBeenCalledWith(
368+
'@codeshift/mod-mylib/path/to/update-imports.js',
369+
expect.arrayContaining([mockPath]),
370+
expect.objectContaining({
371+
parser: 'babel',
372+
extensions: 'js',
373+
}),
374+
);
375+
});
376+
377+
it('should run multiple presets of the same package', async () => {
378+
await main([mockPath], {
379+
packages: 'mylib#update-formatting#update-imports',
380+
parser: 'babel',
381+
extensions: 'js',
382+
});
383+
384+
expect(jscodeshift.run).toHaveBeenCalledTimes(2);
385+
expect(jscodeshift.run).toHaveBeenCalledWith(
386+
'@codeshift/mod-mylib/path/to/update-formatting.js',
387+
expect.arrayContaining([mockPath]),
388+
expect.objectContaining({
389+
parser: 'babel',
390+
extensions: 'js',
391+
}),
392+
);
393+
expect(jscodeshift.run).toHaveBeenCalledWith(
394+
'@codeshift/mod-mylib/path/to/update-imports.js',
395+
expect.arrayContaining([mockPath]),
396+
expect.objectContaining({
397+
parser: 'babel',
398+
extensions: 'js',
399+
}),
400+
);
401+
});
402+
403+
it('should throw when preset is not found', async () => {
404+
expect.assertions(1);
405+
406+
try {
407+
await main([mockPath], {
408+
packages: 'mylib#does-not-exist',
409+
parser: 'babel',
410+
extensions: 'js',
411+
});
412+
} catch (error) {
413+
// @ts-ignore
414+
expect(error.message).toMatch(
415+
'Invalid preset provided to the --packages flag. Unable to resolve preset "does-not-exist" for package "mylib"',
416+
);
417+
}
418+
});
419+
});
302420
});

0 commit comments

Comments
 (0)