@graphql-eslint/eslint-plugin 3.15.0 → 3.16.0-alpha-20230209004557-0da21db

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,
@@ -68,7 +68,7 @@ exports.rule = {
68
68
  definitions.push({ type, node });
69
69
  }
70
70
  },
71
- 'Program:exit'() {
71
+ 'Document:exit'() {
72
72
  var _a, _b;
73
73
  for (const { node, type } of definitions.slice(1) /* ignore first definition */) {
74
74
  let name = (0, utils_js_1.pascalCase)(type);
@@ -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
- const ruleTests = eslint_1.Linter.version.startsWith('8')
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);
@@ -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,
@@ -65,7 +65,7 @@ export const rule = {
65
65
  definitions.push({ type, node });
66
66
  }
67
67
  },
68
- 'Program:exit'() {
68
+ 'Document:exit'() {
69
69
  var _a, _b;
70
70
  for (const { node, type } of definitions.slice(1) /* ignore first definition */) {
71
71
  let name = pascalCase(type);
@@ -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
- const ruleTests = Linter.version.startsWith('8')
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-eslint/eslint-plugin",
3
- "version": "3.15.0",
3
+ "version": "3.16.0-alpha-20230209004557-0da21db",
4
4
  "description": "GraphQL plugin for ESLint",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
@@ -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<{
@@ -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<{
@@ -0,0 +1,2 @@
1
+ import { GraphQLESLintRule } from '../types.cjs';
2
+ export declare const rule: GraphQLESLintRule;
@@ -0,0 +1,2 @@
1
+ import { GraphQLESLintRule } from '../types.js';
2
+ export declare const rule: GraphQLESLintRule;
@@ -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
  };
@@ -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
  };