Skip to content

Commit e8c0f4f

Browse files
committed
refactor: autoFixService
1 parent 26957df commit e8c0f4f

File tree

37 files changed

+946
-12
lines changed

37 files changed

+946
-12
lines changed

.pnp.js

+300
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

plugin/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,12 @@
6363
"@babel/preset-typescript": "^7.12.7",
6464
"@babel/runtime": "^7.12.5",
6565
"@cometjs/eslint-plugin": "^0.2.1",
66+
"@cometjs/jest-utils": "^0.1.0",
6667
"@types/async": "^3.2.5",
6768
"@types/common-tags": "^1.8.0",
6869
"@types/graphql": "^14.5.0",
6970
"@types/jest": "^26.0.19",
71+
"@types/jest-when": "^2.7.2",
7072
"@types/node": "^14.14.19",
7173
"@typescript-eslint/eslint-plugin": "^4.11.1",
7274
"@typescript-eslint/parser": "^4.11.1",
@@ -76,6 +78,7 @@
7678
"gatsby": "^2.30.1",
7779
"graphql": "^14.7.0",
7880
"jest": "^26.6.3",
81+
"jest-when": "^3.1.0",
7982
"redux": "^4.0.5",
8083
"typescript": "^4.1.3",
8184
"utility-types": "^3.10.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Machine, actions } from 'xstate';
2+
3+
import type {
4+
AutoFixContext,
5+
AutoFixEvent,
6+
AutoFixConfig,
7+
} from './types';
8+
9+
const machine = Machine<AutoFixContext, AutoFixEvent>({
10+
initial: 'pending',
11+
states: {
12+
pending: {
13+
invoke: {
14+
src: 'autoFixCode',
15+
onDone: 'settled',
16+
},
17+
},
18+
settled: {
19+
type: 'final',
20+
entry: 'reportRejectedResults',
21+
},
22+
},
23+
});
24+
25+
interface MakeAutoFixCodeScheduler {
26+
(args: {
27+
config: AutoFixConfig,
28+
}): typeof machine;
29+
}
30+
export const makeAutoFixCodeScheduler: MakeAutoFixCodeScheduler = ({ config }) => {
31+
return machine.withConfig(config);
32+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { when } from 'jest-when';
2+
import { stripIndent } from 'common-tags';
3+
import { deeplyMock } from '@cometjs/jest-utils';
4+
5+
import { autoFixFiles } from './service';
6+
7+
const [commonMock] = deeplyMock<typeof import('../../common')>('common');
8+
9+
describe('autoFixFiles', () => {
10+
it('should insert type definitions automatically for TypeScript files', async () => {
11+
when(commonMock.readFile)
12+
.calledWith('use-static-query.ts').mockResolvedValueOnce(stripIndent`
13+
const data = useStaticQuery(graphql\`
14+
query Test {
15+
test
16+
}
17+
\`)
18+
`)
19+
.calledWith('static-query-component.tsx').mockResolvedValueOnce(stripIndent`
20+
return (
21+
<StaticQuery
22+
query={graphql\`
23+
query Test {
24+
test
25+
}
26+
\`}
27+
render={data => (
28+
<TestComponent test={data.test} />
29+
)}
30+
/>
31+
);
32+
`);
33+
34+
await expect(autoFixFiles({
35+
files: ['use-static-query.ts', 'static-query-component.tsx'],
36+
// @ts-ignore
37+
pluginOptions: {
38+
language: 'typescript',
39+
namespace: 'GatsbyTypes',
40+
},
41+
})).resolves.toEqual([
42+
{
43+
status: 'fullfilled',
44+
value: undefined,
45+
},
46+
{
47+
status: 'fullfilled',
48+
value: undefined,
49+
},
50+
]);
51+
52+
expect(commonMock.writeFile).toBeCalledWith('use-static-query.ts', stripIndent`
53+
const data = useStaticQuery<TestQuery>(graphql\`
54+
query Test {
55+
test
56+
}
57+
\`);
58+
`);
59+
60+
expect(commonMock.writeFile).toBeCalledWith('static-query-component.tsx', stripIndent`
61+
return (
62+
<StaticQuery<TestQuery>
63+
query={graphql\`
64+
query Test {
65+
test
66+
}
67+
\`}
68+
render={data => (
69+
<TestComponent test={data.test} />
70+
)}
71+
/>
72+
);
73+
`);
74+
});
75+
});
+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import path from 'path';
2+
import { mapOption } from '@cometjs/core';
3+
import { readFile, writeFile } from '../../common';
4+
5+
import type { AutoFixContext } from './types';
6+
7+
/**
8+
* (?<CallExpressionName>useStaticQuery
9+
* (?<TypeTemplate><
10+
* (?<TypeArgument>\S*)
11+
* >)?
12+
* )
13+
* \([\s\S]*?
14+
* graphql
15+
* (?<TemplateLiteral>`\s*?
16+
* (?<QueryDefinitionStart>query
17+
* (?<QueryName>\S*)
18+
* [^{]?\{
19+
* )
20+
* [^`]*?
21+
* `)
22+
*/
23+
const STATIC_QUERY_HOOK_REGEXP = /(?<CallExpressionName>useStaticQuery(?<TypeTemplate><(?<TypeArgument>\S*)>)?)\([\s\S]*?graphql(?<TemplateLiteral>`\s*?(?<QueryDefinitionStart>query (?<QueryName>\S*)[^{]{)[^`]*?`)/g;
24+
25+
/**
26+
* (?<JsxTagOpening><StaticQuery
27+
* (?<TagTypeTemplate><
28+
* (?<TagTypeArgument>\S+)
29+
* >)?
30+
* )
31+
* [\s\S]+?
32+
* query={
33+
* [\s\S]*?
34+
* graphql
35+
* (?<TemplateLiteral>`\s*?
36+
* (?<QueryDefinitionStart>query
37+
* (?<QueryName>\S*)
38+
* [^{]?\{
39+
* )
40+
* [^`]*?
41+
* `)
42+
*/
43+
const STATIC_QUERY_COMPONENT_REGEXP = /(?<JsxTagOpening><StaticQuery(?<TagTypeTemplate><(?<TagTypeArgument>\S+)>)?)[\s\S]+?query={[\s\S]*?graphql(?<TemplateLiteral>`\s*?(?<QueryDefinitionStart>query (?<QueryName>\S*)[^{]?\{)[^`]*`)/g;
44+
45+
interface FixCodeFn {
46+
(args: {
47+
code: string,
48+
namespace: string,
49+
}): string;
50+
}
51+
const fixTypeScript: FixCodeFn = ({ code, namespace }) => {
52+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
53+
return code
54+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
55+
.replace(STATIC_QUERY_HOOK_REGEXP, (substring: string, ...args: any[]) => {
56+
const { length: l, [l - 1]: groups } = args;
57+
return substring.replace(
58+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
59+
groups['CallExpressionName'],
60+
// eslint-disable-next-line
61+
`useStaticQuery<${namespace}.${groups['QueryName']}Query>`,
62+
);
63+
})
64+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
65+
.replace(STATIC_QUERY_COMPONENT_REGEXP, (substring: string, ...args: any[]) => {
66+
const { length: l, [l - 1]: groups } = args;
67+
return substring.replace(
68+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
69+
groups['JsxTagOpening'],
70+
// eslint-disable-next-line
71+
`<StaticQuery<${namespace}.${groups['QueryName']}Query>`,
72+
);
73+
});
74+
};
75+
const fixFlow: FixCodeFn = ({ code, namespace }) => {
76+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
77+
return code
78+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
79+
.replace(STATIC_QUERY_HOOK_REGEXP, (substring: string, ...args: any[]) => {
80+
const { length: l, [l - 1]: groups } = args;
81+
return substring.replace(
82+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
83+
groups['CallExpressionName'],
84+
// eslint-disable-next-line
85+
`useStaticQuery<${namespace}$${groups['QueryName']}Query>`,
86+
);
87+
})
88+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
89+
.replace(STATIC_QUERY_COMPONENT_REGEXP, (substring: string, ...args: any[]) => {
90+
const { length: l, [l - 1]: groups } = args;
91+
return substring.replace(
92+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
93+
groups['JsxTagOpening'],
94+
// eslint-disable-next-line
95+
`<StaticQuery<${namespace}$${groups['QueryName']}Query>`,
96+
);
97+
});
98+
};
99+
100+
const lookupFirstComment = (code: string) => {
101+
const result = /(\/\/(.*)|\/\*([\s\S]*)\*\/)/.exec(code);
102+
return mapOption(result, ([match]) => match);
103+
};
104+
105+
const hasFlowComment = (code: string) => {
106+
const firstComment = lookupFirstComment(code);
107+
return mapOption(firstComment, {
108+
some: () => true,
109+
none: () => false,
110+
});
111+
};
112+
113+
const isJavaScriptFile = (file: string): boolean => {
114+
const ext = path.extname(file);
115+
return /jsx?/.test(ext);
116+
};
117+
118+
const isTypeScriptFile = (file: string): boolean => {
119+
const ext = path.extname(file);
120+
return /tsx?/.test(ext);
121+
};
122+
123+
interface AutoFixFileFn {
124+
(args: {
125+
file: string,
126+
namespace: string,
127+
}): Promise<void>;
128+
}
129+
130+
const autoFixTypeScriptFile: AutoFixFileFn = async ({ file, namespace }) => {
131+
const code = await readFile(file);
132+
const fixed = fixTypeScript({ code, namespace });
133+
if (code !== fixed) {
134+
await writeFile(file, fixed);
135+
}
136+
};
137+
138+
const autoFixFlowFile: AutoFixFileFn = async ({ file, namespace }) => {
139+
const code = await readFile(file);
140+
if (!hasFlowComment(code)) {
141+
return;
142+
}
143+
144+
const fixed = fixFlow({ code, namespace });
145+
if (code !== fixed) {
146+
await writeFile(file, fixed);
147+
}
148+
};
149+
150+
export const autoFixFiles = ({
151+
files,
152+
pluginOptions: {
153+
language,
154+
namespace,
155+
},
156+
}: AutoFixContext): Promise<Array<PromiseSettledResult<void>>> => {
157+
if (language === 'typescript') {
158+
return Promise.allSettled(
159+
files
160+
.filter(isTypeScriptFile)
161+
.map(file => autoFixTypeScriptFile({ file, namespace })),
162+
);
163+
} else {
164+
return Promise.allSettled(
165+
files
166+
.filter(isJavaScriptFile)
167+
.map(file => autoFixFlowFile({ file, namespace })),
168+
);
169+
}
170+
};
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type {
2+
ActionObject,
3+
ActionFunction,
4+
ServiceConfig,
5+
AnyEventObject,
6+
} from 'xstate';
7+
import type { Unwrap } from '@cometjs/core';
8+
import type { RequiredPluginOptions } from '../../../plugin-utils';
9+
import type { autoFixFiles } from './service';
10+
11+
export type AutoFixContext = {
12+
files: string[],
13+
pluginOptions: RequiredPluginOptions,
14+
};
15+
16+
export type AutoFixEvent = (
17+
| { type: 'done.invoke.autoFixFiles', data: Unwrap<ReturnType<typeof autoFixFiles>> }
18+
);
19+
20+
export type AutoFixActionName = (
21+
| 'reportAutoFixResults'
22+
);
23+
24+
export type AutoFixActionMap = Record<
25+
AutoFixActionName,
26+
(
27+
| ActionObject<AutoFixContext, AutoFixEvent>
28+
| ActionFunction<AutoFixContext, AutoFixEvent>
29+
)
30+
>;
31+
32+
export type AutoFixServiceName = (
33+
| 'autoFixFiles'
34+
);
35+
36+
export type AutoFixServiceMap = Record<
37+
AutoFixServiceName,
38+
ServiceConfig<AutoFixContext, AutoFixEvent>
39+
>;
40+
41+
export type AutoFixConfig = {
42+
actions: AutoFixActionMap,
43+
services: AutoFixServiceMap,
44+
};

plugin/tsconfig.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "../tsconfig.json"
3+
}

0 commit comments

Comments
 (0)