@graphql-eslint/eslint-plugin 3.15.0 → 3.16.0-alpha-20230209112929-0bd2d9e
Sign up to get free protection for your applications and to get access to all the features.
- package/cjs/rules/index.js +2 -0
- package/cjs/rules/lone-executable-definition.js +1 -1
- package/cjs/rules/require-import-fragment.js +115 -0
- package/cjs/testkit.js +1 -19
- package/esm/rules/index.js +2 -0
- package/esm/rules/lone-executable-definition.js +1 -1
- package/esm/rules/require-import-fragment.js +111 -0
- package/esm/testkit.js +1 -19
- package/package.json +1 -1
- package/typings/rules/index.d.cts +1 -0
- package/typings/rules/index.d.ts +1 -0
- package/typings/rules/require-import-fragment.d.cts +2 -0
- package/typings/rules/require-import-fragment.d.ts +2 -0
- package/typings/testkit.d.cts +2 -2
- package/typings/testkit.d.ts +2 -2
package/cjs/rules/index.js
CHANGED
@@ -31,6 +31,7 @@ const require_deprecation_reason_js_1 = require("./require-deprecation-reason.js
|
|
31
31
|
const require_description_js_1 = require("./require-description.js");
|
32
32
|
const require_field_of_type_query_in_mutation_result_js_1 = require("./require-field-of-type-query-in-mutation-result.js");
|
33
33
|
const require_id_when_available_js_1 = require("./require-id-when-available.js");
|
34
|
+
const require_import_fragment_js_1 = require("./require-import-fragment.js");
|
34
35
|
const require_nullable_fields_with_oneof_js_1 = require("./require-nullable-fields-with-oneof.js");
|
35
36
|
const require_type_pattern_with_oneof_js_1 = require("./require-type-pattern-with-oneof.js");
|
36
37
|
const selection_set_depth_js_1 = require("./selection-set-depth.js");
|
@@ -65,6 +66,7 @@ exports.rules = {
|
|
65
66
|
'require-description': require_description_js_1.rule,
|
66
67
|
'require-field-of-type-query-in-mutation-result': require_field_of_type_query_in_mutation_result_js_1.rule,
|
67
68
|
'require-id-when-available': require_id_when_available_js_1.rule,
|
69
|
+
'require-import-fragment': require_import_fragment_js_1.rule,
|
68
70
|
'require-nullable-fields-with-oneof': require_nullable_fields_with_oneof_js_1.rule,
|
69
71
|
'require-type-pattern-with-oneof': require_type_pattern_with_oneof_js_1.rule,
|
70
72
|
'selection-set-depth': selection_set_depth_js_1.rule,
|
@@ -0,0 +1,115 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.rule = void 0;
|
4
|
+
const tslib_1 = require("tslib");
|
5
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
6
|
+
const utils_js_1 = require("../utils.js");
|
7
|
+
const RULE_ID = 'require-import-fragment';
|
8
|
+
const SUGGESTION_ID = 'add-import-expression';
|
9
|
+
exports.rule = {
|
10
|
+
meta: {
|
11
|
+
type: 'suggestion',
|
12
|
+
docs: {
|
13
|
+
category: 'Operations',
|
14
|
+
description: 'Require fragments to be imported via an import expression.',
|
15
|
+
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
|
16
|
+
examples: [
|
17
|
+
{
|
18
|
+
title: 'Incorrect',
|
19
|
+
code: /* GraphQL */ `
|
20
|
+
query {
|
21
|
+
user {
|
22
|
+
...UserFields
|
23
|
+
}
|
24
|
+
}
|
25
|
+
`,
|
26
|
+
},
|
27
|
+
{
|
28
|
+
title: 'Incorrect',
|
29
|
+
code: /* GraphQL */ `
|
30
|
+
# import 'post-fields.fragment.graphql'
|
31
|
+
query {
|
32
|
+
user {
|
33
|
+
...UserFields
|
34
|
+
}
|
35
|
+
}
|
36
|
+
`,
|
37
|
+
},
|
38
|
+
{
|
39
|
+
title: 'Incorrect',
|
40
|
+
code: /* GraphQL */ `
|
41
|
+
# import UserFields from 'post-fields.fragment.graphql'
|
42
|
+
query {
|
43
|
+
user {
|
44
|
+
...UserFields
|
45
|
+
}
|
46
|
+
}
|
47
|
+
`,
|
48
|
+
},
|
49
|
+
{
|
50
|
+
title: 'Correct',
|
51
|
+
code: /* GraphQL */ `
|
52
|
+
# import UserFields from 'user-fields.fragment.graphql'
|
53
|
+
query {
|
54
|
+
user {
|
55
|
+
...UserFields
|
56
|
+
}
|
57
|
+
}
|
58
|
+
`,
|
59
|
+
},
|
60
|
+
],
|
61
|
+
requiresSiblings: true,
|
62
|
+
isDisabledForAllConfig: true,
|
63
|
+
},
|
64
|
+
hasSuggestions: true,
|
65
|
+
messages: {
|
66
|
+
[RULE_ID]: 'Expected "{{fragmentName}}" fragment to be imported.',
|
67
|
+
[SUGGESTION_ID]: 'Add import expression for "{{fragmentName}}".',
|
68
|
+
},
|
69
|
+
schema: [],
|
70
|
+
},
|
71
|
+
create(context) {
|
72
|
+
const comments = context.getSourceCode().getAllComments();
|
73
|
+
const siblings = (0, utils_js_1.requireSiblingsOperations)(RULE_ID, context);
|
74
|
+
const filePath = context.getFilename();
|
75
|
+
return {
|
76
|
+
'FragmentSpread > .name'(node) {
|
77
|
+
var _a;
|
78
|
+
const fragmentName = node.value;
|
79
|
+
const fragmentsFromSiblings = siblings.getFragment(fragmentName);
|
80
|
+
for (const comment of comments) {
|
81
|
+
if (comment.type !== 'Line')
|
82
|
+
continue;
|
83
|
+
// 1. could start with extra whitespace
|
84
|
+
// 2. match both named/default import
|
85
|
+
const isPossibleImported = new RegExp(`^\\s*import\\s+(${fragmentName}\\s+from\\s+)?['"]`).test(comment.value);
|
86
|
+
if (!isPossibleImported)
|
87
|
+
continue;
|
88
|
+
const extractedImportPath = (_a = comment.value.match(/(["'])((?:\1|.)*?)\1/)) === null || _a === void 0 ? void 0 : _a[2];
|
89
|
+
if (!extractedImportPath)
|
90
|
+
continue;
|
91
|
+
const importPath = path_1.default.join(path_1.default.dirname(filePath), extractedImportPath);
|
92
|
+
const hasInSiblings = fragmentsFromSiblings.some(source => source.filePath === importPath);
|
93
|
+
if (hasInSiblings)
|
94
|
+
return;
|
95
|
+
}
|
96
|
+
const fragmentInSameFile = fragmentsFromSiblings.some(source => source.filePath === filePath);
|
97
|
+
if (fragmentInSameFile)
|
98
|
+
return;
|
99
|
+
const suggestedFilePaths = fragmentsFromSiblings.length
|
100
|
+
? fragmentsFromSiblings.map(o => path_1.default.relative(path_1.default.dirname(filePath), o.filePath))
|
101
|
+
: ['CHANGE_ME.graphql'];
|
102
|
+
context.report({
|
103
|
+
node,
|
104
|
+
messageId: RULE_ID,
|
105
|
+
data: { fragmentName },
|
106
|
+
suggest: suggestedFilePaths.map(suggestedPath => ({
|
107
|
+
messageId: SUGGESTION_ID,
|
108
|
+
data: { fragmentName },
|
109
|
+
fix: fixer => fixer.insertTextBeforeRange([0, 0], `# import ${fragmentName} from '${suggestedPath}'\n`),
|
110
|
+
})),
|
111
|
+
});
|
112
|
+
},
|
113
|
+
};
|
114
|
+
},
|
115
|
+
};
|
package/cjs/testkit.js
CHANGED
@@ -29,25 +29,7 @@ class GraphQLRuleTester extends eslint_1.RuleTester {
|
|
29
29
|
return (0, fs_1.readFileSync)((0, path_1.resolve)(__dirname, `../tests/mocks/${path}`), 'utf-8');
|
30
30
|
}
|
31
31
|
runGraphQLTests(ruleId, rule, tests) {
|
32
|
-
|
33
|
-
? tests
|
34
|
-
: {
|
35
|
-
valid: tests.valid.map(test => {
|
36
|
-
if (typeof test === 'string') {
|
37
|
-
return test;
|
38
|
-
}
|
39
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
40
|
-
const { name, ...testCaseOptions } = test;
|
41
|
-
return testCaseOptions;
|
42
|
-
}),
|
43
|
-
invalid: tests.invalid.map(test => {
|
44
|
-
// ESLint 7 throws an error on CI - Unexpected top-level property "name"
|
45
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
46
|
-
const { name, ...testCaseOptions } = test;
|
47
|
-
return testCaseOptions;
|
48
|
-
}),
|
49
|
-
};
|
50
|
-
super.run(ruleId, rule, ruleTests);
|
32
|
+
super.run(ruleId, rule, tests);
|
51
33
|
const linter = new eslint_1.Linter();
|
52
34
|
linter.defineRule(ruleId, rule);
|
53
35
|
const hasOnlyTest = [...tests.valid, ...tests.invalid].some(t => typeof t !== 'string' && t.only);
|
package/esm/rules/index.js
CHANGED
@@ -28,6 +28,7 @@ import { rule as requireDeprecationReason } from './require-deprecation-reason.j
|
|
28
28
|
import { rule as requireDescription } from './require-description.js';
|
29
29
|
import { rule as requireFieldOfTypeQueryInMutationResult } from './require-field-of-type-query-in-mutation-result.js';
|
30
30
|
import { rule as requireIdWhenAvailable } from './require-id-when-available.js';
|
31
|
+
import { rule as requireImportFragment } from './require-import-fragment.js';
|
31
32
|
import { rule as requireNullableFieldsWithOneof } from './require-nullable-fields-with-oneof.js';
|
32
33
|
import { rule as requireTypePatternWithOneof } from './require-type-pattern-with-oneof.js';
|
33
34
|
import { rule as selectionSetDepth } from './selection-set-depth.js';
|
@@ -62,6 +63,7 @@ export const rules = {
|
|
62
63
|
'require-description': requireDescription,
|
63
64
|
'require-field-of-type-query-in-mutation-result': requireFieldOfTypeQueryInMutationResult,
|
64
65
|
'require-id-when-available': requireIdWhenAvailable,
|
66
|
+
'require-import-fragment': requireImportFragment,
|
65
67
|
'require-nullable-fields-with-oneof': requireNullableFieldsWithOneof,
|
66
68
|
'require-type-pattern-with-oneof': requireTypePatternWithOneof,
|
67
69
|
'selection-set-depth': selectionSetDepth,
|
@@ -0,0 +1,111 @@
|
|
1
|
+
import path from 'path';
|
2
|
+
import { requireSiblingsOperations } from '../utils.js';
|
3
|
+
const RULE_ID = 'require-import-fragment';
|
4
|
+
const SUGGESTION_ID = 'add-import-expression';
|
5
|
+
export const rule = {
|
6
|
+
meta: {
|
7
|
+
type: 'suggestion',
|
8
|
+
docs: {
|
9
|
+
category: 'Operations',
|
10
|
+
description: 'Require fragments to be imported via an import expression.',
|
11
|
+
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
|
12
|
+
examples: [
|
13
|
+
{
|
14
|
+
title: 'Incorrect',
|
15
|
+
code: /* GraphQL */ `
|
16
|
+
query {
|
17
|
+
user {
|
18
|
+
...UserFields
|
19
|
+
}
|
20
|
+
}
|
21
|
+
`,
|
22
|
+
},
|
23
|
+
{
|
24
|
+
title: 'Incorrect',
|
25
|
+
code: /* GraphQL */ `
|
26
|
+
# import 'post-fields.fragment.graphql'
|
27
|
+
query {
|
28
|
+
user {
|
29
|
+
...UserFields
|
30
|
+
}
|
31
|
+
}
|
32
|
+
`,
|
33
|
+
},
|
34
|
+
{
|
35
|
+
title: 'Incorrect',
|
36
|
+
code: /* GraphQL */ `
|
37
|
+
# import UserFields from 'post-fields.fragment.graphql'
|
38
|
+
query {
|
39
|
+
user {
|
40
|
+
...UserFields
|
41
|
+
}
|
42
|
+
}
|
43
|
+
`,
|
44
|
+
},
|
45
|
+
{
|
46
|
+
title: 'Correct',
|
47
|
+
code: /* GraphQL */ `
|
48
|
+
# import UserFields from 'user-fields.fragment.graphql'
|
49
|
+
query {
|
50
|
+
user {
|
51
|
+
...UserFields
|
52
|
+
}
|
53
|
+
}
|
54
|
+
`,
|
55
|
+
},
|
56
|
+
],
|
57
|
+
requiresSiblings: true,
|
58
|
+
isDisabledForAllConfig: true,
|
59
|
+
},
|
60
|
+
hasSuggestions: true,
|
61
|
+
messages: {
|
62
|
+
[RULE_ID]: 'Expected "{{fragmentName}}" fragment to be imported.',
|
63
|
+
[SUGGESTION_ID]: 'Add import expression for "{{fragmentName}}".',
|
64
|
+
},
|
65
|
+
schema: [],
|
66
|
+
},
|
67
|
+
create(context) {
|
68
|
+
const comments = context.getSourceCode().getAllComments();
|
69
|
+
const siblings = requireSiblingsOperations(RULE_ID, context);
|
70
|
+
const filePath = context.getFilename();
|
71
|
+
return {
|
72
|
+
'FragmentSpread > .name'(node) {
|
73
|
+
var _a;
|
74
|
+
const fragmentName = node.value;
|
75
|
+
const fragmentsFromSiblings = siblings.getFragment(fragmentName);
|
76
|
+
for (const comment of comments) {
|
77
|
+
if (comment.type !== 'Line')
|
78
|
+
continue;
|
79
|
+
// 1. could start with extra whitespace
|
80
|
+
// 2. match both named/default import
|
81
|
+
const isPossibleImported = new RegExp(`^\\s*import\\s+(${fragmentName}\\s+from\\s+)?['"]`).test(comment.value);
|
82
|
+
if (!isPossibleImported)
|
83
|
+
continue;
|
84
|
+
const extractedImportPath = (_a = comment.value.match(/(["'])((?:\1|.)*?)\1/)) === null || _a === void 0 ? void 0 : _a[2];
|
85
|
+
if (!extractedImportPath)
|
86
|
+
continue;
|
87
|
+
const importPath = path.join(path.dirname(filePath), extractedImportPath);
|
88
|
+
const hasInSiblings = fragmentsFromSiblings.some(source => source.filePath === importPath);
|
89
|
+
if (hasInSiblings)
|
90
|
+
return;
|
91
|
+
}
|
92
|
+
const fragmentInSameFile = fragmentsFromSiblings.some(source => source.filePath === filePath);
|
93
|
+
if (fragmentInSameFile)
|
94
|
+
return;
|
95
|
+
const suggestedFilePaths = fragmentsFromSiblings.length
|
96
|
+
? fragmentsFromSiblings.map(o => path.relative(path.dirname(filePath), o.filePath))
|
97
|
+
: ['CHANGE_ME.graphql'];
|
98
|
+
context.report({
|
99
|
+
node,
|
100
|
+
messageId: RULE_ID,
|
101
|
+
data: { fragmentName },
|
102
|
+
suggest: suggestedFilePaths.map(suggestedPath => ({
|
103
|
+
messageId: SUGGESTION_ID,
|
104
|
+
data: { fragmentName },
|
105
|
+
fix: fixer => fixer.insertTextBeforeRange([0, 0], `# import ${fragmentName} from '${suggestedPath}'\n`),
|
106
|
+
})),
|
107
|
+
});
|
108
|
+
},
|
109
|
+
};
|
110
|
+
},
|
111
|
+
};
|
package/esm/testkit.js
CHANGED
@@ -28,25 +28,7 @@ export class GraphQLRuleTester extends RuleTester {
|
|
28
28
|
return readFileSync(resolve(__dirname, `../tests/mocks/${path}`), 'utf-8');
|
29
29
|
}
|
30
30
|
runGraphQLTests(ruleId, rule, tests) {
|
31
|
-
|
32
|
-
? tests
|
33
|
-
: {
|
34
|
-
valid: tests.valid.map(test => {
|
35
|
-
if (typeof test === 'string') {
|
36
|
-
return test;
|
37
|
-
}
|
38
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
39
|
-
const { name, ...testCaseOptions } = test;
|
40
|
-
return testCaseOptions;
|
41
|
-
}),
|
42
|
-
invalid: tests.invalid.map(test => {
|
43
|
-
// ESLint 7 throws an error on CI - Unexpected top-level property "name"
|
44
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
45
|
-
const { name, ...testCaseOptions } = test;
|
46
|
-
return testCaseOptions;
|
47
|
-
}),
|
48
|
-
};
|
49
|
-
super.run(ruleId, rule, ruleTests);
|
31
|
+
super.run(ruleId, rule, tests);
|
50
32
|
const linter = new Linter();
|
51
33
|
linter.defineRule(ruleId, rule);
|
52
34
|
const hasOnlyTest = [...tests.valid, ...tests.invalid].some(t => typeof t !== 'string' && t.only);
|
package/package.json
CHANGED
@@ -88,6 +88,7 @@ export declare const rules: {
|
|
88
88
|
'require-id-when-available': import("../types.js").GraphQLESLintRule<{
|
89
89
|
fieldName?: string | string[] | undefined;
|
90
90
|
}[], true>;
|
91
|
+
'require-import-fragment': import("../types.js").GraphQLESLintRule<[], false>;
|
91
92
|
'require-nullable-fields-with-oneof': import("../types.js").GraphQLESLintRule<[], false>;
|
92
93
|
'require-type-pattern-with-oneof': import("../types.js").GraphQLESLintRule<[], false>;
|
93
94
|
'selection-set-depth': import("../types.js").GraphQLESLintRule<{
|
package/typings/rules/index.d.ts
CHANGED
@@ -88,6 +88,7 @@ export declare const rules: {
|
|
88
88
|
'require-id-when-available': import("../types.js").GraphQLESLintRule<{
|
89
89
|
fieldName?: string | string[] | undefined;
|
90
90
|
}[], true>;
|
91
|
+
'require-import-fragment': import("../types.js").GraphQLESLintRule<[], false>;
|
91
92
|
'require-nullable-fields-with-oneof': import("../types.js").GraphQLESLintRule<[], false>;
|
92
93
|
'require-type-pattern-with-oneof': import("../types.js").GraphQLESLintRule<[], false>;
|
93
94
|
'selection-set-depth': import("../types.js").GraphQLESLintRule<{
|
package/typings/testkit.d.cts
CHANGED
@@ -5,11 +5,11 @@ import { GraphQLESLintRule, ParserOptions } from './types.cjs';
|
|
5
5
|
export type GraphQLESLintRuleListener<WithTypeInfo extends boolean = false> = Record<string, any> & {
|
6
6
|
[K in keyof ASTKindToNode]?: (node: GraphQLESTreeNode<ASTKindToNode[K], WithTypeInfo>) => void;
|
7
7
|
};
|
8
|
-
export type GraphQLValidTestCase<Options> = Omit<RuleTester.ValidTestCase, 'options' | 'parserOptions'> & {
|
8
|
+
export type GraphQLValidTestCase<Options = []> = Omit<RuleTester.ValidTestCase, 'options' | 'parserOptions'> & {
|
9
9
|
options?: Options;
|
10
10
|
parserOptions?: Omit<ParserOptions, 'filePath'>;
|
11
11
|
};
|
12
|
-
export type GraphQLInvalidTestCase<T> = GraphQLValidTestCase<T> & {
|
12
|
+
export type GraphQLInvalidTestCase<T = []> = GraphQLValidTestCase<T> & {
|
13
13
|
errors: (RuleTester.TestCaseError | string)[] | number;
|
14
14
|
output?: string | null;
|
15
15
|
};
|
package/typings/testkit.d.ts
CHANGED
@@ -5,11 +5,11 @@ import { GraphQLESLintRule, ParserOptions } from './types.js';
|
|
5
5
|
export type GraphQLESLintRuleListener<WithTypeInfo extends boolean = false> = Record<string, any> & {
|
6
6
|
[K in keyof ASTKindToNode]?: (node: GraphQLESTreeNode<ASTKindToNode[K], WithTypeInfo>) => void;
|
7
7
|
};
|
8
|
-
export type GraphQLValidTestCase<Options> = Omit<RuleTester.ValidTestCase, 'options' | 'parserOptions'> & {
|
8
|
+
export type GraphQLValidTestCase<Options = []> = Omit<RuleTester.ValidTestCase, 'options' | 'parserOptions'> & {
|
9
9
|
options?: Options;
|
10
10
|
parserOptions?: Omit<ParserOptions, 'filePath'>;
|
11
11
|
};
|
12
|
-
export type GraphQLInvalidTestCase<T> = GraphQLValidTestCase<T> & {
|
12
|
+
export type GraphQLInvalidTestCase<T = []> = GraphQLValidTestCase<T> & {
|
13
13
|
errors: (RuleTester.TestCaseError | string)[] | number;
|
14
14
|
output?: string | null;
|
15
15
|
};
|