Skip to content

Commit 89652a5

Browse files
committed
POC
1 parent 29fc85b commit 89652a5

1 file changed

Lines changed: 120 additions & 33 deletions

File tree

  • packages/operation-location-migration/src

packages/operation-location-migration/src/preset.ts

Lines changed: 120 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ import {
77
} from '@graphql-codegen/plugin-helpers';
88
import { pascalCase } from 'change-case-all';
99
import { OperationTypeNode, visit } from 'graphql';
10-
import { CallExpression, ImportSpecifier, Project, SyntaxKind } from 'ts-morph';
10+
import {
11+
CallExpression,
12+
ImportDeclaration,
13+
ImportSpecifier,
14+
Project,
15+
SyntaxKind,
16+
} from 'ts-morph';
1117
import type { TypedPresetConfig } from './config';
1218

1319
export const preset: Types.OutputPreset<TypedPresetConfig> = {
@@ -18,22 +24,37 @@ export const preset: Types.OutputPreset<TypedPresetConfig> = {
1824
presetConfig,
1925
profiler = createNoopProfiler(),
2026
}) => {
21-
// lo
2227
const absoluteTsConfigFilePath = path.join(
2328
cwd(),
2429
presetConfig.tsConfigFilePath
2530
);
2631
if (!fs.existsSync(absoluteTsConfigFilePath)) {
2732
throw new Error('Requires tsconfig');
2833
}
34+
const gqlTagPath = path.posix.join(
35+
baseOutputDir,
36+
presetConfig.artifactDirectory
37+
); // FIXME: handle absolute path
38+
2939
const tsProject = new Project({
3040
tsConfigFilePath: absoluteTsConfigFilePath,
3141
});
3242
const tsSourceFiles = tsProject.getSourceFiles();
3343

3444
const hooksToReplace: Record<
3545
string,
36-
{ name: string; importFrom: path.ParsedPath }
46+
{
47+
hookName: string;
48+
hookType:
49+
| 'useQuery'
50+
| 'useLazyQuery'
51+
| 'useSuspenseQuery'
52+
| 'useMutation'
53+
| 'useSubscription';
54+
importFrom: path.ParsedPath;
55+
operationName: string;
56+
documentSDL: string;
57+
}
3758
> = {};
3859

3960
documents.forEach((documentFile) => {
@@ -47,74 +68,77 @@ export const preset: Types.OutputPreset<TypedPresetConfig> = {
4768
visit(documentFile.document, {
4869
OperationDefinition(node) {
4970
if (!node.name) {
50-
throw new Error('Anonimous operation!'); // FIXME: should not throw
71+
throw new Error('Anonymous operation!'); // FIXME: should not throw
5172
}
5273

74+
const operationName = pascalCase(node.name.value);
75+
5376
if (node.operation === OperationTypeNode.QUERY) {
54-
const queryHookName = `use${pascalCase(node.name.value)}Query`;
77+
// Query
78+
const queryHookName = `use${operationName}Query`;
5579
hooksToReplace[queryHookName] = {
56-
name: queryHookName,
80+
hookName: queryHookName,
81+
hookType: 'useQuery',
5782
importFrom: documentPath,
83+
operationName,
84+
documentSDL,
5885
};
5986

6087
const lazyHookName = `use${pascalCase(node.name.value)}LazyQuery`;
6188
hooksToReplace[lazyHookName] = {
62-
name: lazyHookName,
89+
hookName: lazyHookName,
90+
hookType: 'useLazyQuery',
6391
importFrom: documentPath,
92+
operationName,
93+
documentSDL,
6494
};
6595

6696
const suspenseHookName = `use${pascalCase(
6797
node.name.value
6898
)}SuspenseQuery`;
6999
hooksToReplace[suspenseHookName] = {
70-
name: suspenseHookName,
100+
hookName: suspenseHookName,
101+
hookType: 'useSuspenseQuery',
71102
importFrom: documentPath,
103+
operationName,
104+
documentSDL,
72105
};
73106
} else {
107+
// Mutation & Subscription
74108
const hookName = `use${pascalCase(node.name.value)}${pascalCase(
75109
node.operation
76110
)}`;
77111
hooksToReplace[hookName] = {
78-
name: hookName,
112+
hookName: hookName,
113+
hookType:
114+
node.operation === OperationTypeNode.MUTATION
115+
? 'useMutation'
116+
: 'useSubscription',
79117
importFrom: documentPath,
118+
operationName,
119+
documentSDL,
80120
};
81121
}
82122
},
83123
});
84-
85-
const gqlTagPath = path.posix.join(
86-
baseOutputDir,
87-
presetConfig.artifactDirectory
88-
); // FIXME: handle absolute path
89-
90-
const newFilename = path.join(
91-
documentPath.dir,
92-
`${documentPath.name}.graphql.ts`
93-
);
94-
const gqlTagImportPath = path.posix.relative(
95-
documentPath.dir,
96-
gqlTagPath
97-
);
98124
});
99125

100126
tsSourceFiles.forEach((tsSourceFile) => {
101-
console.log(
102-
'***filepath: ',
103-
path.basename(tsSourceFile.getFilePath()),
104-
'START:'
105-
);
106127
// find imports
107128
const fileMetadata: {
108129
hasNodeToReplace: boolean;
130+
needsToImport: string[];
109131
functionsToReplace: Record<
110132
string,
111133
{
134+
importDeclarationNode: ImportDeclaration;
112135
importSpecifierNode: ImportSpecifier;
113136
callExpression: CallExpression | null;
114137
}
115138
>;
116139
} = {
117140
hasNodeToReplace: false,
141+
needsToImport: [],
118142
functionsToReplace: {},
119143
};
120144

@@ -127,6 +151,7 @@ export const preset: Types.OutputPreset<TypedPresetConfig> = {
127151
if (hooksToReplace[importSpecifier.getName()]) {
128152
// FIXME: we are assuming same name means must replace. Should check import path as well?
129153
fileMetadata.functionsToReplace[importSpecifier.getName()] = {
154+
importDeclarationNode: node,
130155
importSpecifierNode: importSpecifier,
131156
callExpression: null,
132157
};
@@ -148,21 +173,83 @@ export const preset: Types.OutputPreset<TypedPresetConfig> = {
148173
fileMetadata.hasNodeToReplace = true;
149174
fileMetadata.functionsToReplace[calledFunctionName].callExpression =
150175
callExpression;
176+
fileMetadata.needsToImport.push(
177+
hooksToReplace[calledFunctionName].hookType
178+
);
151179
}
152180
});
153181

154182
if (!fileMetadata.hasNodeToReplace) {
155183
return;
156184
}
157185

158-
console.log(
159-
'***filepath: ',
160-
path.basename(tsSourceFile.getFilePath()),
161-
'END\n'
186+
// Codemod files to co-locate the document nodes
187+
tsSourceFile.addImportDeclaration({
188+
moduleSpecifier: '@apollo/client/react', // FIXME: option to import from different module
189+
namedImports: fileMetadata.needsToImport.map((importName) => ({
190+
name: importName,
191+
})),
192+
});
193+
194+
tsSourceFile.addImportDeclaration({
195+
moduleSpecifier: path.posix.relative(
196+
path.dirname(tsSourceFile.getFilePath()), // TODO: would this break in windows?
197+
gqlTagPath
198+
), // TODO: add relative path to codegen gql
199+
namedImports: [{ name: presetConfig.gqlTagName }],
200+
});
201+
202+
Object.values(fileMetadata.functionsToReplace).forEach(
203+
(functionToReplace) => {
204+
if (functionToReplace.callExpression) {
205+
const graphqlDocument =
206+
hooksToReplace[functionToReplace.importSpecifierNode.getName()];
207+
const documentNodeName = `${graphqlDocument.operationName}Doc`;
208+
209+
functionToReplace.callExpression
210+
.getFirstDescendantByKindOrThrow(SyntaxKind.Identifier)
211+
.replaceWithText(graphqlDocument.hookType);
212+
functionToReplace.callExpression.insertArgument(
213+
0,
214+
documentNodeName
215+
);
216+
217+
// Remove import specifier of hooks, and if no specifiers left, remove the whole import declaration
218+
functionToReplace.importSpecifierNode.remove();
219+
if (
220+
functionToReplace.importDeclarationNode.getDescendantsOfKind(
221+
SyntaxKind.ImportSpecifier
222+
).length === 0
223+
) {
224+
functionToReplace.importDeclarationNode.remove();
225+
}
226+
227+
// Add documentNode to file
228+
const lastImportIndex = tsSourceFile.getDescendantsOfKind(
229+
SyntaxKind.ImportDeclaration
230+
).length;
231+
tsSourceFile.insertStatements(
232+
lastImportIndex,
233+
`const ${documentNodeName} = graphql(\`${graphqlDocument.documentSDL}\`)`
234+
);
235+
}
236+
}
162237
);
238+
239+
console.log('*** after update:');
240+
console.log(tsSourceFile.getFullText());
241+
242+
console.log('END\n');
163243
});
164244

165-
return [];
245+
return tsSourceFiles.map((tsSourceFile) => ({
246+
filename: tsSourceFile.getFilePath(),
247+
pluginMap: { add: addPlugin },
248+
plugins: [{ add: { content: tsSourceFile.getFullText() } }],
249+
config: {},
250+
schema,
251+
documents: [],
252+
}));
166253
},
167254
};
168255

0 commit comments

Comments
 (0)