Skip to content

Commit 75ec7d1

Browse files
authored
fix false positive case in possible-type-extension rule when schema… (#857)
1 parent 8bffe47 commit 75ec7d1

24 files changed

+267
-131
lines changed

.changeset/tidy-kangaroos-sell.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': minor
3+
---
4+
5+
fix false positive case in `possible-type-extension` rule when schema is separate into multiple files

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ module.exports = {
5252
},
5353
},
5454
{
55-
files: ['**/tests/mocks/*.{ts,js}'],
55+
files: ['**/tests/mocks/**/*.{ts,js}'],
5656
rules: {
5757
'@typescript-eslint/no-unused-vars': 'off',
5858
},

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ npm install --save-dev @graphql-eslint/eslint-plugin
4242

4343
> Make sure you have `graphql` dependency in your project.
4444
45-
### Configuration
45+
## Configuration
4646

4747
To get started, define an override in your ESLint config to apply this plugin to `.graphql` files. Add the [rules](docs/README.md) you want applied.
4848

@@ -65,7 +65,7 @@ To get started, define an override in your ESLint config to apply this plugin to
6565

6666
If your GraphQL definitions are defined only in `.graphql` files, and you're only using rules that apply to individual files, you should be good to go 👍. If you would like use a remote schema or use rules that apply across the entire collection of definitions at once, see [here](#using-a-remote-schema-or-rules-with-constraints-that-span-the-entire-schema).
6767

68-
#### Tell ESLint to apply this plugin to GraphQL definitions defined in code files
68+
### Apply this plugin to GraphQL definitions defined in code files
6969

7070
If you are defining GraphQL schema or GraphQL operations in code files, you'll want to define an additional override to extend the functionality of this plugin to the schema and operations in those files.
7171

@@ -90,7 +90,7 @@ If you are defining GraphQL schema or GraphQL operations in code files, you'll w
9090

9191
Under the hood, specifying the `@graphql-eslint/graphql` processor for code files will cause `graphql-eslint/graphql` to extract the schema and operation definitions from these files into virtual GraphQL documents with `.graphql` extensions. This will allow the overrides you've defined for `.graphql` files, via `"files": ["*.graphql"]`, to get applied to the definitions defined in your code files.
9292

93-
#### Using a remote schema or rules with constraints that span the entire schema
93+
### Extended linting rules with GraphQL Schema
9494

9595
Some rules require an understanding of the entire schema at once. For example, [no-unreachable-types](https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-unreachable-types.md#no-unreachable-types) checks that all types are reachable by root-level fields.
9696

@@ -120,7 +120,7 @@ The parser allows you to specify a json file / graphql files(s) / url / raw stri
120120
121121
> Some rules require type information to operate, it's marked in the docs for each rule!
122122
123-
#### Extended linting rules with siblings operations
123+
### Extended linting rules with siblings operations
124124

125125
While implementing this tool, we had to find solutions for a better integration of the GraphQL ecosystem and ESLint core.
126126

@@ -180,7 +180,7 @@ You can find a list of [ESLint directives here](https://eslint.org/docs/2.13.1/u
180180

181181
You can find a complete list of [all available rules here](docs/README.md).
182182

183-
## Deprecated Rules
183+
### Deprecated Rules
184184

185185
See [docs/deprecated-rules.md](docs/deprecated-rules.md).
186186

@@ -201,7 +201,7 @@ See [docs/deprecated-rules.md](docs/deprecated-rules.md).
201201

202202
> If you are in a monorepo project, you probably need both sets of rules.
203203

204-
## Config usage
204+
### Config usage
205205

206206
For example, to enable the `schema-recommended` config, enable it in your `.eslintrc` file with the `extends` option:
207207

docs/rules/possible-type-extension.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
- Category: `Schema`
44
- Rule name: `@graphql-eslint/possible-type-extension`
5-
- Requires GraphQL Schema: `false` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
5+
- Requires GraphQL Schema: `true` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)
66
- Requires GraphQL Operations: `false` [ℹ️](../../README.md#extended-linting-rules-with-siblings-operations)
77

88
A type extension is only valid if the type is defined and has the same kind.

packages/plugin/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"@graphql-tools/code-file-loader": "7.2.3",
3939
"@graphql-tools/graphql-tag-pluck": "7.1.4",
4040
"@graphql-tools/utils": "8.5.5",
41+
"chalk": "4.1.2",
4142
"graphql-config": "4.1.0",
4243
"graphql-depth-limit": "1.1.0",
4344
"lodash.lowercase": "4.3.0"

packages/plugin/src/graphql-config.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { GraphQLConfig, GraphQLExtensionDeclaration, loadConfigSync, SchemaPoint
22
import { CodeFileLoader } from '@graphql-tools/code-file-loader';
33
import { ParserOptions } from './types';
44

5-
let graphqlConfig: GraphQLConfig;
5+
let graphQLConfig: GraphQLConfig;
66

7-
export function loadGraphqlConfig(options: ParserOptions): GraphQLConfig {
7+
export function loadGraphQLConfig(options: ParserOptions): GraphQLConfig {
88
// We don't want cache config on test environment
99
// Otherwise schema and documents will be same for all tests
10-
if (process.env.NODE_ENV !== 'test' && graphqlConfig) {
11-
return graphqlConfig;
10+
if (process.env.NODE_ENV !== 'test' && graphQLConfig) {
11+
return graphQLConfig;
1212
}
1313

1414
const onDiskConfig = options.skipGraphQLConfig
@@ -19,25 +19,27 @@ export function loadGraphqlConfig(options: ParserOptions): GraphQLConfig {
1919
extensions: [addCodeFileLoaderExtension],
2020
});
2121

22-
graphqlConfig =
22+
const configOptions = options.projects
23+
? { projects: options.projects }
24+
: {
25+
schema: (options.schema || '') as SchemaPointer, // if `schema` is `undefined` will throw error `Project 'default' not found`
26+
documents: options.documents || options.operations,
27+
extensions: options.extensions,
28+
include: options.include,
29+
exclude: options.exclude,
30+
};
31+
32+
graphQLConfig =
2333
onDiskConfig ||
2434
new GraphQLConfig(
2535
{
26-
config: options.projects
27-
? { projects: options.projects }
28-
: {
29-
schema: (options.schema || '') as SchemaPointer, // if `schema` is `undefined` will throw error `Project 'default' not found`
30-
documents: options.documents || options.operations,
31-
extensions: options.extensions,
32-
include: options.include,
33-
exclude: options.exclude,
34-
},
36+
config: configOptions,
3537
filepath: 'virtual-config',
3638
},
3739
[addCodeFileLoaderExtension]
3840
);
3941

40-
return graphqlConfig;
42+
return graphQLConfig;
4143
}
4244

4345
const addCodeFileLoaderExtension: GraphQLExtensionDeclaration = api => {

packages/plugin/src/parser.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ import { GraphQLESLintParseResult, ParserOptions, ParserServices } from './types
66
import { extractTokens } from './utils';
77
import { getSchema } from './schema';
88
import { getSiblingOperations } from './sibling-operations';
9-
import { loadGraphqlConfig } from './graphql-config';
9+
import { loadGraphQLConfig } from './graphql-config';
1010
import { getReachableTypes, getUsedFields } from './graphql-ast';
1111

1212
export function parse(code: string, options?: ParserOptions): Linter.ESLintParseResult['ast'] {
1313
return parseForESLint(code, options).ast;
1414
}
1515

1616
export function parseForESLint(code: string, options: ParserOptions = {}): GraphQLESLintParseResult {
17-
const gqlConfig = loadGraphqlConfig(options);
17+
const gqlConfig = loadGraphQLConfig(options);
1818
const schema = getSchema(options, gqlConfig);
1919
const parserServices: ParserServices = {
2020
hasTypeInfo: schema !== null,
@@ -49,21 +49,18 @@ export function parseForESLint(code: string, options: ParserOptions = {}): Graph
4949
},
5050
};
5151
} catch (e) {
52+
e.message = `[graphql-eslint] ${e.message}`;
5253
// In case of GraphQL parser error, we report it to ESLint as a parser error that matches the requirements
5354
// of ESLint. This will make sure to display it correctly in IDEs and lint results.
5455
if (e instanceof GraphQLError) {
5556
const eslintError = {
5657
index: e.positions[0],
5758
lineNumber: e.locations[0].line,
5859
column: e.locations[0].column,
59-
message: `[graphql-eslint]: ${e.message}`,
60+
message: e.message,
6061
};
61-
6262
throw eslintError;
6363
}
64-
65-
e.message = `[graphql-eslint]: ${e.message}`;
66-
6764
throw e;
6865
}
6966
}

packages/plugin/src/processors/code-files.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Linter } from 'eslint';
22
import { parseCode } from '@graphql-tools/graphql-tag-pluck';
3+
import { logger } from '../utils';
34

45
const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];
56

@@ -45,8 +46,7 @@ export function createGraphqlProcessor(): Linter.Processor {
4546
return blocks;
4647
}
4748
} catch (e) {
48-
// eslint-disable-next-line no-console
49-
console.warn(`[graphql-eslint/processor]: Unable to process file "${filename}": `, e);
49+
logger.warn(`Unable to process file "${filename}"`, e);
5050
}
5151
}
5252

packages/plugin/src/rules/graphql-js-validation.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { AST } from 'eslint';
22
import {
33
Kind,
4-
ASTNode,
54
TypeInfo,
65
DocumentNode,
76
GraphQLSchema,
@@ -14,13 +13,11 @@ import {
1413
} from 'graphql';
1514
import { validateSDL } from 'graphql/validation/validate';
1615
import { GraphQLESLintRule, GraphQLESLintRuleContext } from '../types';
17-
import { getLocation, requireGraphQLSchemaFromContext, requireSiblingsOperations } from '../utils';
18-
import { GraphQLESTreeNode } from '../estree-parser';
16+
import { getLocation, requireGraphQLSchemaFromContext, requireSiblingsOperations, logger } from '../utils';
1917

2018
function validateDocument(
21-
sourceNode: GraphQLESTreeNode<ASTNode>,
2219
context: GraphQLESLintRuleContext,
23-
schema: GraphQLSchema | null,
20+
schema: GraphQLSchema | null = null,
2421
documentNode: DocumentNode,
2522
rule: ValidationRule
2623
): void {
@@ -58,7 +55,8 @@ function validateDocument(
5855
}
5956
} catch (e) {
6057
context.report({
61-
node: sourceNode,
58+
// Report on first character
59+
loc: { column: 0, line: 1 },
6260
message: e.message,
6361
});
6462
}
@@ -167,21 +165,22 @@ const validationToRule = (
167165
},
168166
},
169167
create(context) {
168+
if (!ruleFn) {
169+
logger.warn(
170+
`You rule "${ruleId}" depends on a GraphQL validation rule "${ruleName}" but it's not available in the "graphql-js" version you are using. Skipping...`
171+
);
172+
return {};
173+
}
174+
170175
return {
171176
Document(node) {
172-
if (!ruleFn) {
173-
// eslint-disable-next-line no-console
174-
console.warn(
175-
`You rule "${ruleId}" depends on a GraphQL validation rule "${ruleName}" but it's not available in the "graphql-js" version you are using. Skipping...`
176-
);
177-
return;
178-
}
179177
const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
178+
180179
const documentNode = getDocumentNode
181180
? getDocumentNode({ ruleId, context, schema, node: node.rawNode() })
182181
: node.rawNode();
183182

184-
validateDocument(node, context, schema, documentNode, ruleFn);
183+
validateDocument(context, schema, documentNode, ruleFn);
185184
},
186185
};
187186
},
@@ -376,6 +375,7 @@ export const GRAPHQL_JS_VALIDATIONS: Record<string, GraphQLESLintRule> = Object.
376375
category: 'Schema',
377376
description: `A type extension is only valid if the type is defined and has the same kind.`,
378377
recommended: false, // TODO: enable after https://github.com/dotansimha/graphql-eslint/issues/787 will be fixed
378+
requiresSchema: true,
379379
}),
380380
validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
381381
category: ['Schema', 'Operations'],

packages/plugin/src/rules/selection-set-depth.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ import { GraphQLESLintRule } from '../types';
22
import depthLimit from 'graphql-depth-limit';
33
import { DocumentNode, FragmentDefinitionNode, GraphQLError, Kind, OperationDefinitionNode } from 'graphql';
44
import { GraphQLESTreeNode } from '../estree-parser';
5-
import { getLocation, requireSiblingsOperations } from '../utils';
5+
import { getLocation, logger, requireSiblingsOperations } from '../utils';
66
import { SiblingOperations } from '../sibling-operations';
77

88
type SelectionSetDepthRuleConfig = { maxDepth: number; ignore?: string[] };
99

10+
const RULE_ID = 'selection-set-depth';
11+
1012
const rule: GraphQLESLintRule<[SelectionSetDepthRuleConfig]> = {
1113
meta: {
1214
docs: {
1315
category: 'Operations',
1416
description: `Limit the complexity of the GraphQL operations solely by their depth. Based on [graphql-depth-limit](https://github.com/stems/graphql-depth-limit).`,
15-
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/selection-set-depth.md',
17+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
1618
requiresSiblings: true,
1719
examples: [
1820
{
@@ -87,11 +89,10 @@ const rule: GraphQLESLintRule<[SelectionSetDepthRuleConfig]> = {
8789
let siblings: SiblingOperations | null = null;
8890

8991
try {
90-
siblings = requireSiblingsOperations('selection-set-depth', context);
92+
siblings = requireSiblingsOperations(RULE_ID, context);
9193
} catch (e) {
92-
// eslint-disable-next-line no-console
93-
console.warn(
94-
`Rule "selection-set-depth" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`
94+
logger.warn(
95+
`Rule "${RULE_ID}" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`
9596
);
9697
}
9798

@@ -121,9 +122,8 @@ const rule: GraphQLESLintRule<[SelectionSetDepthRuleConfig]> = {
121122
},
122123
});
123124
} catch (e) {
124-
// eslint-disable-next-line no-console
125-
console.warn(
126-
`Rule "selection-set-depth" check failed due to a missing siblings operations. For more info: http://bit.ly/graphql-eslint-operations`,
125+
logger.warn(
126+
`Rule "${RULE_ID}" check failed due to a missing siblings operations. For more info: http://bit.ly/graphql-eslint-operations`,
127127
e
128128
);
129129
}

0 commit comments

Comments
 (0)