Skip to content

Commit d1a40e1

Browse files
committed
added testkit and some tests
1 parent 8af1d65 commit d1a40e1

File tree

12 files changed

+203
-97
lines changed

12 files changed

+203
-97
lines changed

.vscode/launch.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "pwa-node",
9+
"request": "launch",
10+
"name": "Debug Tests",
11+
"skipFiles": [
12+
"<node_internals>/**"
13+
],
14+
"program": "${workspaceFolder}/node_modules/.bin/jest",
15+
"preLaunchTask": "tsc: build - tsconfig.json",
16+
"outFiles": [
17+
]
18+
}
19+
]
20+
}

example/.eslintrc.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
{
2-
"plugins": ["@graphql-eslint"],
32
"processor": "@graphql-eslint/graphql",
4-
"rules": {
5-
"@graphql-eslint/no-anonymous-operations": "warn"
6-
},
73
"overrides": [
84
{
95
"files": ["./**/*.graphql"],
106
"parser": "@graphql-eslint/parser",
117
"parserOptions": {
12-
"schemaPath": "./**/schema.graphql"
8+
"schema": "./**/schema.graphql"
139
},
1410
"plugins": ["@graphql-eslint"],
1511
"rules": {

example/query.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@ const myQuery = gql`
1010
}
1111
`;
1212

13+
const myOther2Query = /* GraphQL */ `
14+
15+
16+
17+
18+
19+
query {
20+
user {
21+
name
22+
}
23+
}
24+
`;
25+
1326
const myOtherQuery = gql`
1427
query a {
1528
user {

packages/graphql-estree/src/converter.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
visit,
1313
visitWithTypeInfo,
1414
TokenKind,
15+
Location,
1516
} from "graphql";
1617
import { Comment } from "estree";
1718

@@ -37,23 +38,32 @@ function hasTypeField<T extends ASTNode>(
3738
return obj && !!(obj as any).type;
3839
}
3940

41+
function stripTokens(location: Location): Pick<Location, "start" | "end"> {
42+
return {
43+
end: location.end,
44+
start: location.start,
45+
};
46+
}
47+
4048
const convertNode = (typeInfo?: TypeInfo) => <T extends ASTNode>(
4149
node: T
4250
): GraphQLESTreeNode<T> => {
51+
const calculatedTypeInfo = typeInfo
52+
? {
53+
argument: typeInfo.getArgument(),
54+
defaultValue: typeInfo.getDefaultValue(),
55+
directive: typeInfo.getDirective(),
56+
enumValue: typeInfo.getEnumValue(),
57+
fieldDef: typeInfo.getFieldDef(),
58+
inputType: typeInfo.getInputType(),
59+
parentInputType: typeInfo.getParentInputType(),
60+
parentType: typeInfo.getParentType(),
61+
gqlType: typeInfo.getType(),
62+
}
63+
: {};
64+
4365
const commonFields = {
44-
typeInfo: typeInfo
45-
? {
46-
argument: typeInfo.getArgument(),
47-
defaultValue: typeInfo.getDefaultValue(),
48-
directive: typeInfo.getDirective(),
49-
enumValue: typeInfo.getEnumValue(),
50-
fieldDef: typeInfo.getFieldDef(),
51-
inputType: typeInfo.getInputType(),
52-
parentInputType: typeInfo.getParentInputType(),
53-
parentType: typeInfo.getParentType(),
54-
gqlType: typeInfo.getType(),
55-
}
56-
: {},
66+
typeInfo: () => calculatedTypeInfo,
5767
leadingComments: convertDescription(node),
5868
loc: convertLocation(node.loc),
5969
range: convertRange(node.loc),
@@ -69,8 +79,8 @@ const convertNode = (typeInfo?: TypeInfo) => <T extends ASTNode>(
6979
...typeFieldSafe,
7080
...commonFields,
7181
type: node.kind,
72-
rawNode: node,
73-
gqlLocation,
82+
// rawNode: () => node,
83+
gqlLocation: stripTokens(gqlLocation),
7484
} as any) as GraphQLESTreeNode<T>;
7585

7686
return estreeNode;
@@ -83,8 +93,8 @@ const convertNode = (typeInfo?: TypeInfo) => <T extends ASTNode>(
8393
...typeFieldSafe,
8494
...commonFields,
8595
type: node.kind,
86-
rawNode: node,
87-
gqlLocation,
96+
// rawNode: () => node,
97+
gqlLocation: stripTokens(gqlLocation),
8898
} as any) as GraphQLESTreeNode<T>;
8999

90100
return estreeNode;

packages/parser/src/parser.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { convertToESTree } from "@graphql-eslint/graphql-estree";
22
import { parseGraphQLSDL } from "@graphql-tools/utils";
3-
import { GraphQLError, GraphQLSchema, TypeInfo } from "graphql";
3+
import { buildSchema, GraphQLError, GraphQLSchema, TypeInfo } from "graphql";
44
import { loadConfigSync, GraphQLProjectConfig } from "graphql-config";
55
import { loadSchemaSync } from "@graphql-tools/load";
66
import { GraphQLFileLoader } from "@graphql-tools/graphql-file-loader";
@@ -9,7 +9,7 @@ import { UrlLoader } from "@graphql-tools/url-loader";
99
import { GraphQLESLintParseResult, ParserOptions } from "@graphql-eslint/types";
1010

1111
const DEFAULT_CONFIG: ParserOptions = {
12-
schemaPath: null,
12+
schema: null,
1313
skipGraphQLConfig: false,
1414
};
1515

@@ -42,12 +42,22 @@ export function parseForESLint(
4242
}
4343
}
4444

45-
if (!schema && config.schemaPath) {
45+
if (!schema && config.schema) {
4646
try {
47-
schema = loadSchemaSync(config.schemaPath, {
47+
schema = loadSchemaSync(config.schema, {
4848
...config,
4949
assumeValidSDL: true,
5050
loaders: [
51+
{
52+
loaderId: () => "direct-string",
53+
canLoad: async () => false,
54+
load: async () => null,
55+
canLoadSync: (pointer) =>
56+
typeof pointer === "string" && pointer.includes("type "),
57+
loadSync: (pointer) => ({
58+
schema: buildSchema(pointer),
59+
}),
60+
},
5161
new GraphQLFileLoader(),
5262
new JsonFileLoader(),
5363
new UrlLoader(),

packages/plugin/src/rules/require-id-when-available.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { GraphQLESLintRule, getBaseType } from "@graphql-eslint/types";
1+
import {
2+
GraphQLESLintRule,
3+
getBaseType,
4+
requireGraphQLSchemaFromContext,
5+
} from "@graphql-eslint/types";
26
import { GraphQLInterfaceType, GraphQLObjectType } from "graphql";
37

48
const REQUIRE_ID_WHEN_AVAILABLE = "REQUIRE_ID_WHEN_AVAILABLE";
@@ -30,15 +34,19 @@ const rule: GraphQLESLintRule<RequireIdWhenAvailableRuleConfig, true> = {
3034
create(context) {
3135
return {
3236
SelectionSet(node) {
37+
requireGraphQLSchemaFromContext(context);
38+
3339
const fieldName =
3440
(context.options[0] || {}).fieldName || DEFAULT_ID_FIELD_NAME;
3541

3642
if (!node.selections || node.selections.length === 0) {
3743
return;
3844
}
3945

40-
if (node.typeInfo && node.typeInfo.gqlType) {
41-
const rawType = getBaseType(node.typeInfo.gqlType);
46+
const typeInfo = node.typeInfo();
47+
48+
if (typeInfo && typeInfo.gqlType) {
49+
const rawType = getBaseType(typeInfo.gqlType);
4250
if (
4351
rawType instanceof GraphQLObjectType ||
4452
rawType instanceof GraphQLInterfaceType
Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
1-
import { GraphQLESLintRule, requireGraphQLSchemaFromContext, GraphQLESlintRuleContext, GraphQLESTreeNode } from '@graphql-eslint/types';
1+
import {
2+
GraphQLESLintRule,
3+
requireGraphQLSchemaFromContext,
4+
GraphQLESlintRuleContext,
5+
GraphQLESTreeNode,
6+
} from "@graphql-eslint/types";
27
import { Kind, validate, GraphQLSchema, DocumentNode } from "graphql";
38

4-
function validateDoc(context: GraphQLESlintRuleContext, schema: GraphQLSchema, documentNode: DocumentNode) {
5-
if (documentNode && documentNode.definitions && documentNode.definitions.length > 0) {
9+
function validateDoc(
10+
context: GraphQLESlintRuleContext,
11+
schema: GraphQLSchema,
12+
documentNode: DocumentNode
13+
) {
14+
if (
15+
documentNode &&
16+
documentNode.definitions &&
17+
documentNode.definitions.length > 0
18+
) {
619
const validationErrors = validate(schema, documentNode);
720

821
for (const error of validationErrors) {
9-
const node = (error.nodes[0] as any as GraphQLESTreeNode<typeof error.nodes[0]>);
22+
const node = (error.nodes[0] as any) as GraphQLESTreeNode<
23+
typeof error.nodes[0]
24+
>;
1025

1126
context.report({
1227
loc: node.loc,
1328
message: error.message,
14-
})
29+
});
1530
}
1631
}
1732
}
@@ -27,19 +42,19 @@ const rule: GraphQLESLintRule = {
2742

2843
validateDoc(context, schema, {
2944
kind: Kind.DOCUMENT,
30-
definitions: [node.rawNode]
45+
definitions: [node.rawNode()],
3146
});
3247
},
3348
FragmentDefinition(node) {
3449
const schema = requireGraphQLSchemaFromContext(context);
35-
50+
3651
validateDoc(context, schema, {
3752
kind: Kind.DOCUMENT,
38-
definitions: [node.rawNode]
53+
definitions: [node.rawNode()],
3954
});
4055
},
41-
}
42-
}
43-
}
56+
};
57+
},
58+
};
4459

4560
export default rule;
Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,46 @@
1-
// import { GraphQLRuleTester } from "@graphql-eslint/types";
2-
// import rule from "../src/rules/require-id-when-available";
1+
import { GraphQLRuleTester } from "@graphql-eslint/types";
2+
import rule from "../src/rules/require-id-when-available";
33

4-
// const ruleTester = new GraphQLRuleTester();
4+
const TEST_SCHEMA = /* GraphQL */ `
5+
type Query {
6+
hasId: HasId!
7+
noId: NoId!
8+
}
59
6-
// ruleTester.runGraphQLTests("require-id-when-available", rule, {
7-
// valid: [
8-
// {
9-
// code: `query test { username }`
10-
// }
11-
// ],
12-
// invalid: [
13-
// // {
14-
// // code: "foo",
15-
// // errors: [
16-
// // {
17-
// // messageId: "avoidName",
18-
// // },
19-
// // ],
20-
// // },
21-
// ],
22-
// });
10+
type NoId {
11+
name: String!
12+
}
13+
14+
type HasId {
15+
id: ID!
16+
name: String!
17+
}
18+
`;
19+
20+
const WITH_SCHEMA = { parserOptions: { schema: TEST_SCHEMA } };
21+
const ruleTester = new GraphQLRuleTester();
22+
23+
ruleTester.runGraphQLTests("require-id-when-available", rule, {
24+
valid: [
25+
{ ...WITH_SCHEMA, code: `query { noId { name } }` },
26+
{ ...WITH_SCHEMA, code: `query { hasId { id name } }` },
27+
{
28+
...WITH_SCHEMA,
29+
code: `query { hasId { name } }`,
30+
options: [{ fieldName: "name" }],
31+
},
32+
],
33+
invalid: [
34+
{
35+
...WITH_SCHEMA,
36+
code: `query { hasId { name } }`,
37+
errors: [{ messageId: "REQUIRE_ID_WHEN_AVAILABLE" }],
38+
},
39+
{
40+
...WITH_SCHEMA,
41+
code: `query { hasId { id } }`,
42+
options: [{ fieldName: "name" }],
43+
errors: [{ messageId: "REQUIRE_ID_WHEN_AVAILABLE" }],
44+
},
45+
],
46+
});

packages/types/src/estree-ast.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,4 @@
1-
import {
2-
ASTNode,
3-
DirectiveNode,
4-
Location,
5-
NameNode,
6-
StringValueNode,
7-
TypeInfo,
8-
TypeNode,
9-
ValueNode,
10-
} from "graphql";
1+
import { ASTNode, Location, TypeInfo, TypeNode, ValueNode } from "graphql";
112
import { BaseNode } from "estree";
123

134
export type SafeGraphQLType<T extends ASTNode | ValueNode> = Omit<
@@ -27,7 +18,7 @@ export type SingleESTreeNode<
2718
gqlLocation: Location;
2819
} & (WithTypeInfo extends true
2920
? {
30-
typeInfo?: {
21+
typeInfo?: () => {
3122
argument?: ReturnType<TypeInfo["getArgument"]>;
3223
defaultValue?: ReturnType<TypeInfo["getDefaultValue"]>;
3324
directive?: ReturnType<TypeInfo["getDirective"]>;
@@ -46,7 +37,7 @@ export type GraphQLESTreeNode<
4637
T extends any,
4738
WithTypeInfo extends boolean = false
4839
> = T extends ASTNode | ValueNode
49-
? { rawNode: T } & {
40+
? { rawNode: () => T } & {
5041
[K in keyof SingleESTreeNode<T, WithTypeInfo>]: SingleESTreeNode<
5142
T,
5243
WithTypeInfo

packages/types/src/parser-types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Linter } from "eslint";
33
import { GraphQLProjectConfig } from "graphql-config";
44

55
export interface ParserOptions extends Omit<GraphQLParseOptions, "noLocation"> {
6-
schemaPath?: string | string[];
6+
schema?: string | string[];
77
skipGraphQLConfig?: boolean;
88
filePath?: string;
99
}
@@ -16,4 +16,4 @@ export type ParserServices = {
1616
graphqlConfigProject: GraphQLProjectConfig | null;
1717
hasTypeInfo: boolean;
1818
schema: GraphQLSchema | null;
19-
};
19+
};

0 commit comments

Comments
 (0)