Skip to content

Commit 0c91b68

Browse files
committed
Add test for generated-typescript-types and fixed bugs resulting from that
1 parent ead1352 commit 0c91b68

File tree

5 files changed

+1838
-52
lines changed

5 files changed

+1838
-52
lines changed

eslint-plugin-relay.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ module.exports = {
5858
'relay/must-colocate-fragment-spreads': 'error',
5959
'relay/function-required-argument': 'error',
6060
'relay/hook-required-argument': 'error'
61-
},
61+
}
6262
},
6363
'ts-strict': {
6464
rules: {
@@ -71,7 +71,7 @@ module.exports = {
7171
'relay/must-colocate-fragment-spreads': 'error',
7272
'relay/function-required-argument': 'error',
7373
'relay/hook-required-argument': 'error'
74-
},
75-
},
74+
}
75+
}
7676
}
7777
};

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121
"graphql": "^14.0.0 || ^15.0.0"
2222
},
2323
"devDependencies": {
24+
"@typescript-eslint/parser": "^5.4.0",
2425
"babel-eslint": "^10.1.0",
2526
"eslint": "^7.8.0",
2627
"eslint-config-prettier": "^6.11.0",
2728
"eslint-plugin-prettier": "^3.1.4",
2829
"mocha": "^9.1.3",
29-
"prettier": "^2.4.1"
30+
"prettier": "^2.4.1",
31+
"typescript": "^4.5.2"
3032
}
3133
}

src/rule-generated-typescript-types.js

Lines changed: 86 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
'use strict';
99

10-
const utils = require('eslint-plugin-relay/src/utils');
10+
const utils = require('./utils');
1111
const shouldLint = utils.shouldLint;
1212
const getGraphQLAST = utils.getGraphQLAST;
1313

@@ -90,7 +90,7 @@ function getPropTypeProperty(
9090
}
9191
visitedProps.add(propType);
9292
const spreadsToVisit = [];
93-
if (propType.type === 'GenericTypeAnnotation') {
93+
if (propType.type === 'TSTypeReference') {
9494
return getPropTypeProperty(
9595
context,
9696
typeAliasMap,
@@ -99,28 +99,14 @@ function getPropTypeProperty(
9999
visitedProps
100100
);
101101
}
102-
if (propType.type !== 'ObjectTypeAnnotation') {
102+
if (propType.type !== 'TSTypeLiteral') {
103103
return null;
104104
}
105-
for (const property of propType.properties) {
105+
for (const property of propType.members) {
106106
if (property.type === 'ObjectTypeSpreadProperty') {
107107
spreadsToVisit.push(property);
108108
} else {
109-
// HACK: Type annotations don't currently expose a 'key' property:
110-
// https://github.com/babel/babel-eslint/issues/307
111-
112-
let tokenIndex = 0;
113-
if (property.static) {
114-
tokenIndex++;
115-
}
116-
if (property.variance) {
117-
tokenIndex++;
118-
}
119-
120-
if (
121-
context.getSourceCode().getFirstToken(property, tokenIndex).value ===
122-
propName
123-
) {
109+
if (property.key.name === propName) {
124110
return property;
125111
}
126112
}
@@ -165,7 +151,7 @@ function validateObjectTypeAnnotation(
165151
propName
166152
);
167153

168-
const atleastOnePropertyExists = !!propType.properties[0];
154+
const atleastOnePropertyExists = !!propType.members[0];
169155

170156
if (!propTypeProperty) {
171157
if (onlyVerify) {
@@ -195,7 +181,7 @@ function validateObjectTypeAnnotation(
195181
if (atleastOnePropertyExists) {
196182
fixes.push(
197183
fixer.insertTextBefore(
198-
propType.properties[0],
184+
propType.members[0],
199185
`${propName}: ${type}, `
200186
)
201187
);
@@ -210,14 +196,54 @@ function validateObjectTypeAnnotation(
210196
return false;
211197
}
212198
if (
213-
propTypeProperty.value.type === 'NullableTypeAnnotation' &&
214-
propTypeProperty.value.typeAnnotation.type === 'GenericTypeAnnotation' &&
215-
propTypeProperty.value.typeAnnotation.id.name === type
199+
propTypeProperty.type === 'TSPropertySignature' &&
200+
propTypeProperty.typeAnnotation.type === 'TSTypeAnnotation'
216201
) {
217-
return true;
202+
// If we have a TSTypeAnnotation here, it must be a TSTypeReference to the generated type, otherwise we have an invalid reference here
203+
if (
204+
propTypeProperty.typeAnnotation.typeAnnotation.type ===
205+
'TSTypeReference' &&
206+
propTypeProperty.typeAnnotation.typeAnnotation.typeName.name === type
207+
) {
208+
return true;
209+
}
210+
211+
if (onlyVerify) {
212+
return false;
213+
}
214+
215+
context.report({
216+
message:
217+
'Component property `{{prop}}` expects to use the generated ' +
218+
'`{{type}}` typescript type. See https://facebook.github.io/relay/docs/en/graphql-in-relay.html#importing-generated-definitions',
219+
data: {
220+
prop: propName,
221+
type
222+
},
223+
fix: options.fix
224+
? fixer => {
225+
const whitespace = ' '.repeat(Component.parent.loc.start.column);
226+
return [
227+
genImportFixer(
228+
fixer,
229+
importFixRange,
230+
type,
231+
options.haste,
232+
whitespace
233+
),
234+
fixer.replaceText(
235+
propTypeProperty.typeAnnotation.typeAnnotation,
236+
type
237+
)
238+
];
239+
}
240+
: null,
241+
loc: Component.loc
242+
});
243+
return false;
218244
}
219245
if (
220-
propTypeProperty.value.type !== 'GenericTypeAnnotation' ||
246+
propTypeProperty.type !== 'TSTypeReference' ||
221247
propTypeProperty.value.id.name !== type
222248
) {
223249
if (onlyVerify) {
@@ -226,7 +252,7 @@ function validateObjectTypeAnnotation(
226252
context.report({
227253
message:
228254
'Component property `{{prop}}` expects to use the generated ' +
229-
'`{{type}}` flow type. See https://facebook.github.io/relay/docs/en/graphql-in-relay.html#importing-generated-definitions',
255+
'`{{type}}` typescript type. See https://facebook.github.io/relay/docs/en/graphql-in-relay.html#importing-generated-definitions',
230256
data: {
231257
prop: propName,
232258
type
@@ -257,7 +283,7 @@ function extractReadOnlyType(genericType) {
257283
let currentType = genericType;
258284
while (
259285
currentType != null &&
260-
currentType.type === 'GenericTypeAnnotation' &&
286+
currentType.type === 'TSTypeReference' &&
261287
currentType.id.name === '$ReadOnly' &&
262288
currentType.typeParameters &&
263289
currentType.typeParameters.type === 'TypeParameterInstantiation' &&
@@ -273,10 +299,10 @@ function resolveTypeAlias(genericType, typeAliasMap) {
273299
let currentType = genericType;
274300
while (
275301
currentType != null &&
276-
currentType.type === 'GenericTypeAnnotation' &&
277-
typeAliasMap[currentType.id.name] != null
302+
currentType.type === 'TSTypeReference' &&
303+
typeAliasMap[currentType.typeName.name] != null
278304
) {
279-
currentType = typeAliasMap[currentType.id.name];
305+
currentType = typeAliasMap[currentType.typeName.name];
280306
}
281307
return currentType;
282308
}
@@ -325,7 +351,7 @@ module.exports = {
325351
if (arg.type === 'Identifier') {
326352
const name = arg.name;
327353
let scope = context.getScope();
328-
while (scope && scope.type != 'global') {
354+
while (scope != null) {
329355
for (const variable of scope.variables) {
330356
if (variable.name === name) {
331357
const definition = variable.defs.find(
@@ -435,8 +461,8 @@ module.exports = {
435461
requires.push(node);
436462
}
437463
},
438-
TypeAlias(node) {
439-
typeAliasMap[node.id.name] = node.right;
464+
TSTypeAliasDeclaration(node) {
465+
typeAliasMap[node.id.name] = node.typeAnnotation;
440466
},
441467

442468
/**
@@ -564,6 +590,23 @@ module.exports = {
564590
});
565591
},
566592

593+
/**
594+
* Find useMutation() calls without type arguments.
595+
*/
596+
'CallExpression[callee.name=useMutation]:not([typeParameters])'(node) {
597+
const queryName = getDefinitionName(node.arguments[0]);
598+
context.report({
599+
node,
600+
message: `The \`useMutation\` hook should be used with an explicit generated Typescript type, e.g.: useMutation<{{queryName}}>(...)`,
601+
data: {
602+
queryName: queryName
603+
},
604+
fix:
605+
queryName != null && options.fix
606+
? createTypeImportFixer(node, queryName, queryName)
607+
: null
608+
});
609+
},
567610
/**
568611
* Find usePaginationFragment() calls without type arguments.
569612
*/
@@ -794,7 +837,7 @@ module.exports = {
794837
// There exists a prop typeAnnotation. Let's look at how it's
795838
// structured
796839
switch (propType.type) {
797-
case 'ObjectTypeAnnotation': {
840+
case 'TSTypeLiteral': {
798841
validateObjectTypeAnnotation(
799842
context,
800843
Component,
@@ -806,7 +849,7 @@ module.exports = {
806849
);
807850
break;
808851
}
809-
case 'GenericTypeAnnotation': {
852+
case 'TSTypeReference': {
810853
const aliasedObjectType = extractReadOnlyType(
811854
resolveTypeAlias(propType, typeAliasMap)
812855
);
@@ -816,7 +859,7 @@ module.exports = {
816859
break;
817860
}
818861
switch (aliasedObjectType.type) {
819-
case 'ObjectTypeAnnotation': {
862+
case 'TSTypeLiteral': {
820863
validateObjectTypeAnnotation(
821864
context,
822865
Component,
@@ -828,23 +871,23 @@ module.exports = {
828871
);
829872
break;
830873
}
831-
case 'IntersectionTypeAnnotation': {
874+
case 'TSIntersectionType': {
832875
const objectTypes = aliasedObjectType.types
833876
.map(intersectedType => {
834-
if (intersectedType.type === 'GenericTypeAnnotation') {
877+
if (intersectedType.type === 'TSTypeReference') {
835878
return extractReadOnlyType(
836879
resolveTypeAlias(intersectedType, typeAliasMap)
837880
);
838881
}
839-
if (intersectedType.type === 'ObjectTypeAnnotation') {
882+
if (intersectedType.type === 'TSTypeLiteral') {
840883
return intersectedType;
841884
}
842885
})
843886
.filter(maybeObjectType => {
844-
// GenericTypeAnnotation may not map to an object type
887+
// TSTypeReference may not map to an object type
845888
return (
846889
maybeObjectType &&
847-
maybeObjectType.type === 'ObjectTypeAnnotation'
890+
maybeObjectType.type === 'TSTypeLiteral'
848891
);
849892
});
850893
if (!objectTypes.length) {
@@ -887,7 +930,7 @@ module.exports = {
887930
context.report({
888931
message:
889932
'Component property `{{prop}}` expects to use the ' +
890-
'generated `{{type}}` flow type. See https://facebook.github.io/relay/docs/en/graphql-in-relay.html#importing-generated-definitions',
933+
'generated `{{type}}` typescript type. See https://facebook.github.io/relay/docs/en/graphql-in-relay.html#importing-generated-definitions',
891934
data: {
892935
prop: propName,
893936
type: importedPropType

0 commit comments

Comments
 (0)