Skip to content

Commit 5a7e9a7

Browse files
author
Dimitri POSTOLOV
authored
1️⃣ fix: make working ESLint directive in files that contains import comments (#508)
1 parent 04e0d56 commit 5a7e9a7

File tree

5 files changed

+113
-176
lines changed

5 files changed

+113
-176
lines changed

.changeset/six-pianos-eat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-eslint/eslint-plugin': patch
3+
---
4+
5+
fix comments in files that contains import comments

packages/plugin/src/parser.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { convertToESTree } from './estree-parser';
2-
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
1+
import { parseGraphQLSDL } from '@graphql-tools/utils';
32
import { GraphQLError, TypeInfo } from 'graphql';
43
import { Linter } from 'eslint';
5-
import fs from 'fs';
4+
import { convertToESTree } from './estree-parser';
65
import { GraphQLESLintParseResult, ParserOptions } from './types';
76
import { extractTokens } from './utils';
87
import { getSchema } from './schema';
@@ -28,13 +27,10 @@ export function parseForESLint(code: string, options?: ParserOptions): GraphQLES
2827

2928
try {
3029
const filePath = options.filePath || '';
31-
const isVirtualFile = !fs.existsSync(options.filePath);
32-
const fileLoader = new GraphQLFileLoader();
3330

34-
const graphqlAst = fileLoader.handleFileContent(code, filePath, {
31+
const graphqlAst = parseGraphQLSDL(filePath, code, {
3532
...options.graphQLParserOptions,
3633
noLocation: false,
37-
skipGraphQLImport: isVirtualFile,
3834
});
3935

4036
const { rootTree, comments } = convertToESTree(graphqlAst.document, schema ? new TypeInfo(schema) : null);
Lines changed: 95 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { GraphQLESLintRule, GraphQLESlintRuleContext } from '../types';
2-
import { requireGraphQLSchemaFromContext } from '../utils';
3-
41
import { validate, GraphQLSchema, DocumentNode, ASTNode, ValidationRule } from 'graphql';
52
import { validateSDL } from 'graphql/validation/validate';
3+
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
4+
import fs from 'fs';
5+
import { GraphQLESLintRule, GraphQLESlintRuleContext } from '../types';
6+
import { requireGraphQLSchemaFromContext } from '../utils';
67
import { GraphQLESTreeNode } from '../estree-parser';
78

89
function extractRuleName(stack: string | undefined): string | null {
@@ -44,11 +45,16 @@ export function validateDoc(
4445
}
4546
}
4647

48+
const isGraphQLImportFile = rawSDL => {
49+
const trimmedRawSDL = rawSDL.trim();
50+
return trimmedRawSDL.startsWith('# import') || trimmedRawSDL.startsWith('#import');
51+
};
52+
4753
const validationToRule = (
4854
name: string,
4955
ruleName: string,
50-
meta: GraphQLESLintRule['meta'],
51-
skipSchema = false
56+
docs: GraphQLESLintRule['meta']['docs'],
57+
getDocumentNode?: (context: GraphQLESlintRuleContext) => DocumentNode | null
5258
): Record<typeof name, GraphQLESLintRule<any, true>> => {
5359
let ruleFn: null | ValidationRule = null;
5460

@@ -62,35 +68,42 @@ const validationToRule = (
6268
}
6369
}
6470

71+
const requiresSchema = docs.requiresSchema ?? true;
72+
6573
return {
6674
[name]: {
6775
meta: {
68-
...meta,
6976
docs: {
7077
category: 'Validation',
71-
requiresSchema: !skipSchema,
78+
requiresSchema,
7279
requiresSiblings: false,
7380
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${name}.md`,
74-
...meta.docs,
81+
...docs,
7582
description:
76-
meta.docs.description +
83+
docs.description +
7784
`\n\n> This rule is a wrapper around a \`graphql-js\` validation function. [You can find it's source code here](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/${ruleName}Rule.ts).`,
7885
},
7986
},
80-
create: context => {
87+
create(context) {
8188
return {
8289
Document(node) {
8390
if (!ruleFn) {
8491
// eslint-disable-next-line no-console
8592
console.warn(
8693
`You rule "${name}" depends on a GraphQL validation rule ("${ruleName}") but it's not available in the "graphql-js" version you are using. Skipping...`
8794
);
88-
8995
return;
9096
}
9197

92-
const schema = skipSchema ? null : requireGraphQLSchemaFromContext(name, context);
93-
validateDoc(node, context, schema, node.rawNode(), [ruleFn], ruleName);
98+
const schema = requiresSchema ? requireGraphQLSchemaFromContext(name, context) : null;
99+
100+
let documentNode: DocumentNode;
101+
const filePath = context.getFilename();
102+
const isVirtualFile = !fs.existsSync(filePath);
103+
if (!isVirtualFile && getDocumentNode) {
104+
documentNode = getDocumentNode(context);
105+
}
106+
validateDoc(node, context, schema, documentNode || node.rawNode(), [ruleFn], ruleName);
94107
},
95108
};
96109
},
@@ -101,32 +114,24 @@ const validationToRule = (
101114
export const GRAPHQL_JS_VALIDATIONS = Object.assign(
102115
{},
103116
validationToRule('executable-definitions', 'ExecutableDefinitions', {
104-
docs: {
105-
description: `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.`,
106-
},
117+
description: `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.`,
107118
}),
108119
validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
109-
docs: {
110-
description: `A GraphQL document is only valid if all fields selected are defined by the parent type, or are an allowed meta field such as __typename.`,
111-
},
120+
description: `A GraphQL document is only valid if all fields selected are defined by the parent type, or are an allowed meta field such as __typename.`,
112121
}),
113122
validationToRule('fragments-on-composite-type', 'FragmentsOnCompositeTypes', {
114-
docs: {
115-
description: `Fragments use a type condition to determine if they apply, since fragments can only be spread into a composite type (object, interface, or union), the type condition must also be a composite type.`,
116-
},
123+
description: `Fragments use a type condition to determine if they apply, since fragments can only be spread into a composite type (object, interface, or union), the type condition must also be a composite type.`,
117124
}),
118125
validationToRule('known-argument-names', 'KnownArgumentNames', {
119-
docs: {
120-
description: `A GraphQL field is only valid if all supplied arguments are defined by that field.`,
121-
},
126+
description: `A GraphQL field is only valid if all supplied arguments are defined by that field.`,
122127
}),
123128
validationToRule('known-directives', 'KnownDirectives', {
124-
docs: {
125-
description: `A GraphQL document is only valid if all \`@directives\` are known by the schema and legally positioned.`,
126-
},
129+
description: `A GraphQL document is only valid if all \`@directives\` are known by the schema and legally positioned.`,
127130
}),
128-
validationToRule('known-fragment-names', 'KnownFragmentNames', {
129-
docs: {
131+
validationToRule(
132+
'known-fragment-names',
133+
'KnownFragmentNames',
134+
{
130135
description: `A GraphQL document is only valid if all \`...Fragment\` fragment spreads refer to fragments defined in the same document.`,
131136
examples: [
132137
{
@@ -171,170 +176,98 @@ export const GRAPHQL_JS_VALIDATIONS = Object.assign(
171176
},
172177
],
173178
},
174-
}),
179+
context => {
180+
const code = context.getSourceCode().text;
181+
if (!isGraphQLImportFile(code)) {
182+
return null;
183+
}
184+
// Import documents if file contains '#import' comments
185+
const fileLoader = new GraphQLFileLoader();
186+
const graphqlAst = fileLoader.handleFileContent(code, context.getFilename(), { noLocation: true });
187+
return graphqlAst.document;
188+
}
189+
),
175190
validationToRule('known-type-names', 'KnownTypeNames', {
176-
docs: {
177-
description: `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.`,
178-
},
191+
description: `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.`,
179192
}),
180193
validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
181-
docs: {
182-
description: `A GraphQL document is only valid if when it contains an anonymous operation (the query short-hand) that it contains only that one operation definition.`,
183-
},
194+
description: `A GraphQL document is only valid if when it contains an anonymous operation (the query short-hand) that it contains only that one operation definition.`,
195+
}),
196+
validationToRule('lone-schema-definition', 'LoneSchemaDefinition', {
197+
description: `A GraphQL document is only valid if it contains only one schema definition.`,
198+
requiresSchema: false,
184199
}),
185-
validationToRule(
186-
'lone-schema-definition',
187-
'LoneSchemaDefinition',
188-
{
189-
docs: {
190-
description: `A GraphQL document is only valid if it contains only one schema definition.`,
191-
},
192-
},
193-
true
194-
),
195200
validationToRule('no-fragment-cycles', 'NoFragmentCycles', {
196-
docs: {
197-
description: `A GraphQL fragment is only valid when it does not have cycles in fragments usage.`,
198-
},
201+
description: `A GraphQL fragment is only valid when it does not have cycles in fragments usage.`,
199202
}),
200203
validationToRule('no-undefined-variables', 'NoUndefinedVariables', {
201-
docs: {
202-
description: `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.`,
203-
},
204+
description: `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.`,
204205
}),
205206
validationToRule('no-unused-fragments', 'NoUnusedFragments', {
206-
docs: {
207-
description: `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.`,
208-
},
207+
description: `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.`,
209208
}),
210209
validationToRule('no-unused-variables', 'NoUnusedVariables', {
211-
docs: {
212-
description: `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.`,
213-
},
210+
description: `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.`,
214211
}),
215212
validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
216-
docs: {
217-
description: `A selection set is only valid if all fields (including spreading any fragments) either correspond to distinct response names or can be merged without ambiguity.`,
218-
},
213+
description: `A selection set is only valid if all fields (including spreading any fragments) either correspond to distinct response names or can be merged without ambiguity.`,
219214
}),
220215
validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
221-
docs: {
222-
description: `A fragment spread is only valid if the type condition could ever possibly be true: if there is a non-empty intersection of the possible parent types, and possible types which pass the type condition.`,
223-
},
216+
description: `A fragment spread is only valid if the type condition could ever possibly be true: if there is a non-empty intersection of the possible parent types, and possible types which pass the type condition.`,
217+
}),
218+
validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
219+
description: `A type extension is only valid if the type is defined and has the same kind.`,
220+
requiresSchema: false,
224221
}),
225-
validationToRule(
226-
'possible-type-extension',
227-
'PossibleTypeExtensions',
228-
{
229-
docs: {
230-
description: `A type extension is only valid if the type is defined and has the same kind.`,
231-
},
232-
},
233-
true
234-
),
235222
validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
236-
docs: {
237-
description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
238-
},
223+
description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
239224
}),
240225
validationToRule('scalar-leafs', 'ScalarLeafs', {
241-
docs: {
242-
description: `A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.`,
243-
},
226+
description: `A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.`,
244227
}),
245228
validationToRule('one-field-subscriptions', 'SingleFieldSubscriptions', {
246-
docs: {
247-
description: `A GraphQL subscription is valid only if it contains a single root field.`,
248-
},
229+
description: `A GraphQL subscription is valid only if it contains a single root field.`,
249230
}),
250231
validationToRule('unique-argument-names', 'UniqueArgumentNames', {
251-
docs: {
252-
description: `A GraphQL field or directive is only valid if all supplied arguments are uniquely named.`,
253-
},
232+
description: `A GraphQL field or directive is only valid if all supplied arguments are uniquely named.`,
233+
}),
234+
validationToRule('unique-directive-names', 'UniqueDirectiveNames', {
235+
description: `A GraphQL document is only valid if all defined directives have unique names.`,
236+
requiresSchema: false,
254237
}),
255-
validationToRule(
256-
'unique-directive-names',
257-
'UniqueDirectiveNames',
258-
{
259-
docs: {
260-
description: `A GraphQL document is only valid if all defined directives have unique names.`,
261-
},
262-
},
263-
true
264-
),
265238
validationToRule('unique-directive-names-per-location', 'UniqueDirectivesPerLocation', {
266-
docs: {
267-
description: `A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.`,
268-
},
239+
description: `A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.`,
240+
}),
241+
validationToRule('unique-enum-value-names', 'UniqueEnumValueNames', {
242+
description: `A GraphQL enum type is only valid if all its values are uniquely named.`,
243+
requiresSchema: false,
244+
}),
245+
validationToRule('unique-field-definition-names', 'UniqueFieldDefinitionNames', {
246+
description: `A GraphQL complex type is only valid if all its fields are uniquely named.`,
247+
requiresSchema: false,
248+
}),
249+
validationToRule('unique-input-field-names', 'UniqueInputFieldNames', {
250+
description: `A GraphQL input object value is only valid if all supplied fields are uniquely named.`,
251+
requiresSchema: false,
252+
}),
253+
validationToRule('unique-operation-types', 'UniqueOperationTypes', {
254+
description: `A GraphQL document is only valid if it has only one type per operation.`,
255+
requiresSchema: false,
256+
}),
257+
validationToRule('unique-type-names', 'UniqueTypeNames', {
258+
description: `A GraphQL document is only valid if all defined types have unique names.`,
259+
requiresSchema: false,
269260
}),
270-
validationToRule(
271-
'unique-enum-value-names',
272-
'UniqueEnumValueNames',
273-
{
274-
docs: {
275-
description: `A GraphQL enum type is only valid if all its values are uniquely named.`,
276-
},
277-
},
278-
true
279-
),
280-
validationToRule(
281-
'unique-field-definition-names',
282-
'UniqueFieldDefinitionNames',
283-
{
284-
docs: {
285-
description: `A GraphQL complex type is only valid if all its fields are uniquely named.`,
286-
},
287-
},
288-
true
289-
),
290-
validationToRule(
291-
'unique-input-field-names',
292-
'UniqueInputFieldNames',
293-
{
294-
docs: {
295-
description: `A GraphQL input object value is only valid if all supplied fields are uniquely named.`,
296-
},
297-
},
298-
true
299-
),
300-
validationToRule(
301-
'unique-operation-types',
302-
'UniqueOperationTypes',
303-
{
304-
docs: {
305-
description: `A GraphQL document is only valid if it has only one type per operation.`,
306-
},
307-
},
308-
true
309-
),
310-
validationToRule(
311-
'unique-type-names',
312-
'UniqueTypeNames',
313-
{
314-
docs: {
315-
description: `A GraphQL document is only valid if all defined types have unique names.`,
316-
},
317-
},
318-
true
319-
),
320261
validationToRule('unique-variable-names', 'UniqueVariableNames', {
321-
docs: {
322-
description: `A GraphQL operation is only valid if all its variables are uniquely named.`,
323-
},
262+
description: `A GraphQL operation is only valid if all its variables are uniquely named.`,
324263
}),
325264
validationToRule('value-literals-of-correct-type', 'ValuesOfCorrectType', {
326-
docs: {
327-
description: `A GraphQL document is only valid if all value literals are of the type expected at their position.`,
328-
},
265+
description: `A GraphQL document is only valid if all value literals are of the type expected at their position.`,
329266
}),
330267
validationToRule('variables-are-input-types', 'VariablesAreInputTypes', {
331-
docs: {
332-
description: `A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).`,
333-
},
268+
description: `A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).`,
334269
}),
335270
validationToRule('variables-in-allowed-position', 'VariablesInAllowedPosition', {
336-
docs: {
337-
description: `Variables passed to field arguments conform to type.`,
338-
},
271+
description: `Variables passed to field arguments conform to type.`,
339272
})
340273
) as Record<string, GraphQLESLintRule>;

0 commit comments

Comments
 (0)