Skip to content

Commit

Permalink
Another solution: do not crash ESLint when schema/siblings contains v…
Browse files Browse the repository at this point in the history
…alidation errors (#1386)

* fix

* update changeset

* update eslint

* Update packages/plugin/src/schema.ts
  • Loading branch information
dimaMachina committed Jan 25, 2023
1 parent af89542 commit c5fb2dc
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 188 deletions.
2 changes: 1 addition & 1 deletion .changeset/config.json
Expand Up @@ -5,7 +5,7 @@
"access": "restricted",
"baseBranch": "master",
"updateInternalDependencies": "patch",
"ignore": ["@graphql-eslint/example-*"],
"ignore": ["@graphql-eslint/example-*", "website"],
"changelog": [
"@changesets/changelog-github",
{
Expand Down
11 changes: 11 additions & 0 deletions .changeset/early-poems-reflect.md
@@ -0,0 +1,11 @@
---
'@graphql-eslint/eslint-plugin': minor
---

The new version no longer crashes on VSCode-ESLint, when schema/siblings contain validation errors,
also, GraphQL-ESLint now has `strict: true` in `tsconfig.json` and extends `@typescript-eslint`
recommended config 🚀

P.S. GraphQL-ESLint now has its own website, all documentation moved here. Also, it contains a new
fancy playground page 💅 for both schema/operations testing
https://the-guild.dev/graphql/eslint/play
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -31,7 +31,7 @@
"chalk": "4.1.2",
"dedent": "0.7.0",
"enquirer": "2.3.6",
"eslint": "8.31.0",
"eslint": "8.32.0",
"eslint-plugin-eslint-plugin": "5.0.7",
"eslint-plugin-tailwindcss": "3.8.2",
"husky": "8.0.3",
Expand All @@ -49,7 +49,7 @@
},
"pnpm": {
"patchedDependencies": {
"eslint@8.31.0": "patches/eslint@8.31.0.patch",
"eslint@8.32.0": "patches/eslint@8.31.0.patch",
"eslint-plugin-eslint-plugin@5.0.7": "patches/eslint-plugin-eslint-plugin@5.0.6.patch",
"json-schema-to-markdown@1.1.1": "patches/json-schema-to-markdown@1.1.1.patch",
"vitest@0.26.3": "patches/vitest@0.26.3.patch"
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin/src/estree-converter/converter.ts
Expand Up @@ -12,7 +12,7 @@ import {
import { GraphQLESTreeNode, TypeInformation } from './types.js';
import { convertLocation } from './utils.js';

export function convertToESTree<T extends DocumentNode>(node: T, schema?: GraphQLSchema) {
export function convertToESTree<T extends DocumentNode>(node: T, schema?: GraphQLSchema | null) {
const typeInfo = schema && new TypeInfo(schema);

const visitor: ASTVisitor = {
Expand Down
24 changes: 15 additions & 9 deletions packages/plugin/src/parser.ts
Expand Up @@ -27,16 +27,22 @@ export function parseForESLint(code: string, options: ParserOptions): GraphQLESL
const realFilepath = filePath.replace(VIRTUAL_DOCUMENT_REGEX, '');
const project = gqlConfig.getProjectForFile(realFilepath);

const schema = project
? getSchema(project, options.schemaOptions)
: typeof options.schema === 'string'
? buildSchema(options.schema)
: null;
let schema: GraphQLSchema | null = null;

const rootTree = convertToESTree(
document,
schema instanceof GraphQLSchema ? schema : undefined,
);
try {
schema = project
? getSchema(project, options.schemaOptions)
: typeof options.schema === 'string'
? buildSchema(options.schema)
: null;
} catch (error) {
if (error instanceof Error) {
error.message = `Error while loading schema: ${error.message}`;
}
throw error;
}

const rootTree = convertToESTree(document, schema);

return {
services: {
Expand Down
34 changes: 12 additions & 22 deletions packages/plugin/src/schema.ts
@@ -1,12 +1,11 @@
import chalk from 'chalk';
import debugFactory from 'debug';
import fg from 'fast-glob';
import { GraphQLSchema } from 'graphql';
import { GraphQLProjectConfig } from 'graphql-config';
import { ModuleCache } from './cache.js';
import { ParserOptions, Pointer, Schema } from './types.js';

const schemaCache = new ModuleCache<Error | GraphQLSchema>();
const schemaCache = new ModuleCache<GraphQLSchema>();
const debug = debugFactory('graphql-eslint:schema');

export function getSchema(
Expand All @@ -25,26 +24,17 @@ export function getSchema(
return cache;
}

let schema: Error | GraphQLSchema;

try {
debug('Loading schema from %o', project.schema);
schema = project.loadSchemaSync(project.schema, 'GraphQLSchema', {
...schemaOptions,
pluckConfig: project.extensions.pluckConfig,
});
if (debug.enabled) {
debug('Schema loaded: %o', schema instanceof GraphQLSchema);
const schemaPaths = fg.sync(project.schema as Pointer, { absolute: true });
debug('Schema pointers %O', schemaPaths);
}
// Do not set error to cache, since cache reload will be done after some `lifetime` seconds
schemaCache.set(schemaKey, schema);
} catch (error) {
if (error instanceof Error) {
error.message = chalk.red(`Error while loading schema: ${error.message}`);
}
schema = error as Error;
debug('Loading schema from %o', project.schema);
const schema = project.loadSchemaSync(project.schema, 'GraphQLSchema', {
...schemaOptions,
pluckConfig: project.extensions.pluckConfig,
});
if (debug.enabled) {
debug('Schema loaded: %o', schema instanceof GraphQLSchema);
const schemaPaths = fg.sync(project.schema as Pointer, { absolute: true });
debug('Schema pointers %O', schemaPaths);
}
schemaCache.set(schemaKey, schema);

return schema;
}
4 changes: 2 additions & 2 deletions packages/plugin/src/types.ts
Expand Up @@ -7,8 +7,8 @@ import { JSONSchema } from 'json-schema-to-ts';
import { SiblingOperations } from './siblings.js';
import { GraphQLESLintRuleListener } from './testkit.js';

export type Schema = Error | GraphQLSchema | null;
export type Pointer = string[] | string;
export type Schema = GraphQLSchema | null;
export type Pointer = string | string[];

export interface ParserOptions {
schema?: Pointer | Record<string, { headers: Record<string, string> }>;
Expand Down
2 changes: 0 additions & 2 deletions packages/plugin/src/utils.ts
Expand Up @@ -28,8 +28,6 @@ export function requireGraphQLSchemaFromContext(
throw new Error(
`Rule \`${ruleId}\` requires \`parserOptions.schema\` to be set and loaded. See https://bit.ly/graphql-eslint-schema for more info`,
);
} else if (schema instanceof Error) {
throw schema;
}
return schema;
}
Expand Down
26 changes: 11 additions & 15 deletions packages/plugin/tests/schema.spec.ts
@@ -1,4 +1,4 @@
import { spawn } from 'node:child_process';
import { ChildProcessWithoutNullStreams, spawn } from 'node:child_process';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { GraphQLSchema, printSchema } from 'graphql';
Expand Down Expand Up @@ -39,8 +39,8 @@ describe('schema', async () => {
});

describe('UrlLoader', () => {
let local;
let url;
let local: ChildProcessWithoutNullStreams;
let url: string;

beforeAll(
() =>
Expand All @@ -64,8 +64,8 @@ describe('schema', async () => {

afterAll(
() =>
new Promise(resolve => {
local.on('close', () => resolve());
new Promise(done => {
local.on('close', () => done());
local.kill();
}),
);
Expand All @@ -75,7 +75,7 @@ describe('schema', async () => {
});

describe('should passe headers', () => {
let schemaUrl;
let schemaUrl: string;
let schemaOptions;

beforeAll(() => {
Expand All @@ -95,27 +95,23 @@ describe('schema', async () => {
},
filePath: '',
});
const error = getSchema(gqlConfig.getDefault()) as Error;
expect(error).toBeInstanceOf(Error);
expect(error.message).toMatch('authorization: "Bearer Foo"');
expect(() => getSchema(gqlConfig.getDefault())).toThrow('authorization: "Bearer Foo"');
});

// https://github.com/B2o5T/graphql-eslint/blob/master/docs/parser-options.md#schemaoptions
it('with `parserOptions.schemaOptions`', () => {
const gqlConfig = loadGraphQLConfig({ schema: schemaUrl, filePath: '' });
const error = getSchema(gqlConfig.getDefault(), schemaOptions) as Error;
expect(error).toBeInstanceOf(Error);
expect(error.message).toMatch('authorization: "Bearer Foo"');
expect(() => getSchema(gqlConfig.getDefault(), schemaOptions)).toThrow(
'authorization: "Bearer Foo"',
);
});
});
});

describe('schema loading', () => {
it('should return Error', () => {
const gqlConfig = loadGraphQLConfig({ schema: 'not-exist.gql', filePath: '' });
const error = getSchema(gqlConfig.getDefault()) as Error;
expect(error).toBeInstanceOf(Error);
expect(error.message).toMatch(
expect(() => getSchema(gqlConfig.getDefault())).toThrow(
'Unable to find any GraphQL type definitions for the following pointers',
);
});
Expand Down

0 comments on commit c5fb2dc

Please sign in to comment.