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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  };