Skip to content

Commit b41afbb

Browse files
committed
Supports the ability to publish and validate presets
1 parent 83923eb commit b41afbb

File tree

6 files changed

+230
-46
lines changed

6 files changed

+230
-46
lines changed

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/validator/src/index.ts

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,51 @@
11
import fs from 'fs-extra';
22
import semver from 'semver';
3-
import * as recast from 'recast';
4-
5-
const packageNameRegex = /^(@[a-z0-9-~][a-z0-9-._~]*__)?[a-z0-9-~][a-z0-9-._~]*$/;
3+
import path from 'path';
64

75
export function isValidPackageName(dir: string) {
8-
return dir.match(packageNameRegex);
6+
return dir.match(/^(@[a-z0-9-~][a-z0-9-._~]*__)?[a-z0-9-~][a-z0-9-._~]*$/);
97
}
108

11-
export async function isValidConfig(path: string) {
12-
const configPath = path + `/codeshift.config.js`;
13-
const source = await fs.readFile(configPath, 'utf8');
14-
const ast = recast.parse(source);
9+
export async function isValidConfig(filePath: string) {
10+
const configPath = path.join(process.cwd(), filePath, 'codeshift.config.js');
11+
// eslint-disable-next-line @typescript-eslint/no-var-requires
12+
let config = require(configPath);
13+
14+
config = !!config.default ? config.default : config;
15+
16+
const invalidSemverIds = [];
17+
const invalidPresetIds = [];
1518

1619
let hasTransforms = false;
17-
let invalidSemverIds = [];
18-
let transformCount = 0;
19-
20-
recast.visit(ast, {
21-
visitProperty(path) {
22-
// @ts-ignore
23-
if (path.node.key.name === 'transforms') {
24-
hasTransforms = true;
25-
// @ts-ignore
26-
const properties = path.node.value.properties;
27-
transformCount = properties.length;
28-
// @ts-ignore
29-
properties.forEach(property => {
30-
if (!semver.valid(property.key.value)) {
31-
invalidSemverIds.push(property.key.value);
32-
}
33-
});
34-
}
35-
36-
return false;
37-
},
38-
});
39-
40-
if (!hasTransforms || !transformCount) {
20+
21+
if (config.transforms && Object.keys(config.transforms).length) {
22+
Object.entries(config.transforms).forEach(([key]) => {
23+
hasTransforms = true;
24+
if (!semver.valid(key)) invalidSemverIds.push(key);
25+
});
26+
}
27+
28+
if (config.presets && Object.keys(config.presets).length) {
29+
hasTransforms = true;
30+
Object.entries(config.presets).forEach(([key]) => {
31+
if (key.includes(' ')) invalidPresetIds.push(key);
32+
});
33+
}
34+
35+
if (!hasTransforms) {
4136
throw new Error(
42-
'At least one transform should be specified for config at "${configPath}"',
37+
`At least one transform should be specified for config at "${configPath}"`,
4338
);
4439
}
4540

4641
if (invalidSemverIds.length) {
4742
throw new Error(`Invalid transform ids found for config at "${configPath}".
48-
Please make sure all transforms are identified by a valid semver version. ie 10.0.0`);
43+
Please make sure all transforms are identified by a valid semver version. ie 10.0.0`);
44+
}
45+
46+
if (invalidPresetIds.length) {
47+
throw new Error(`Invalid preset ids found for config at "${configPath}".
48+
Please make sure all presets are kebab case and contain no spaces or special characters. ie sort-imports-by-scope`);
4949
}
5050
}
5151

scripts/docs.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ interface Config {
1212
transforms: {
1313
[key: string]: any;
1414
};
15+
presets: {
16+
[key: string]: any;
17+
};
1518
}
1619

1720
interface DocsData {
@@ -36,6 +39,8 @@ function main() {
3639

3740
cleanTargetDir(DOCS_PATH);
3841

42+
console.log(data);
43+
3944
data.forEach(({ name, config }) => {
4045
const safeName = name.replace('@', '');
4146
const rawName = name.replace('__', '/');
@@ -57,23 +62,44 @@ ${config.maintainers.map(
5762
maintainer => `- [${maintainer}](https://github.com/${maintainer})`,
5863
)}
5964
65+
## Transforms
66+
6067
${Object.keys(config.transforms)
6168
.map(
62-
key => `## ${key}
69+
key => `### ${key}
6370
6471
[Source](https://github.com/CodeshiftCommunity/CodeshiftCommunity/tree/main/community/${urlSafeName}) | [Report an issue](https://github.com/CodeshiftCommunity/CodeshiftCommunity/issues/new?title=${safeName}@${key})
6572
6673
Migrates ${packageLink} to version ${key}.
6774
6875
### Usage
6976
77+
\`\`\`
78+
$ @codeshift/cli --packages ${name}@${key} path/to/source
79+
\`\`\`
80+
`,
81+
)
82+
.join('')}
83+
84+
${config.presets &&
85+
`
86+
## Presets
87+
88+
${Object.keys(config.presets)
89+
.map(
90+
key => `### ${key}
91+
92+
[Source](https://github.com/CodeshiftCommunity/CodeshiftCommunity/tree/main/community/${urlSafeName}) | [Report an issue](https://github.com/CodeshiftCommunity/CodeshiftCommunity/issues/new?title=${safeName}@${key})
93+
94+
### Usage
7095
7196
\`\`\`
72-
npx @codeshift/cli --packages ${name}@${key} path/to/source
97+
$ @codeshift/cli --packages ${name}#${key} path/to/source
7398
\`\`\`
7499
`,
75100
)
76101
.join('')}
102+
`}
77103
`,
78104
);
79105
});

scripts/validate.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import fs, { lstatSync, existsSync } from 'fs-extra';
2-
import semver from 'semver';
32
import { isValidPackageName, isValidConfig } from '@codeshift/validator';
43

54
async function main(path: string) {
@@ -18,15 +17,6 @@ async function main(path: string) {
1817

1918
const subDirectories = await fs.readdir(`${path}/${dir}`);
2019
subDirectories.forEach(async subDir => {
21-
if (
22-
lstatSync(`${path}/${dir}/${subDir}`).isDirectory() &&
23-
!semver.valid(subDir)
24-
) {
25-
throw new Error(
26-
`Codemod folder name "${subDir}" has an invalid version name. Please make sure the file name is valid semver. For example "18.0.0"`,
27-
);
28-
}
29-
3020
if (
3121
lstatSync(`${path}/${dir}/${subDir}`).isDirectory() &&
3222
!existsSync(`${path}/${dir}/${subDir}/transform.ts`) &&

0 commit comments

Comments
 (0)