Skip to content

Commit cf59b0a

Browse files
authored
feat: reload schema/documents cache (only for **current project**) in VSCode (#1222)
* feat: reload schema/documents cache (only for **current project**) in VSCode * Update packages/plugin/src/documents.ts * Update packages/plugin/src/schema.ts
1 parent 8568313 commit cf59b0a

File tree

13 files changed

+93
-84
lines changed

13 files changed

+93
-84
lines changed

.changeset/wet-ads-rush.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+
feat: reload schema/documents cache (only for **current project**) in VSCode

.github/renovate.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
{
22
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
33
"extends": ["github>the-guild-org/shared-config:renovate"],
4-
"automerge": true
4+
"lockFileMaintenance": {
5+
"enabled": true,
6+
"automerge": true,
7+
"automergeType": "pr",
8+
"platformAutomerge": true
9+
}
510
}

packages/plugin/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@
9292
"graphql/validation/rules/ValuesOfCorrectTypeRule",
9393
"graphql/validation/rules/VariablesAreInputTypesRule",
9494
"graphql/validation/rules/VariablesInAllowedPositionRule",
95-
"graphql/language"
95+
"graphql/language",
96+
"minimatch"
9697
]
9798
},
9899
"publishConfig": {

packages/plugin/src/cache.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Based on the `eslint-plugin-import`'s cache
2+
// https://github.com/import-js/eslint-plugin-import/blob/main/utils/ModuleCache.js
3+
import debugFactory from 'debug';
4+
5+
const log = debugFactory('graphql-eslint:ModuleCache');
6+
7+
export class ModuleCache<T, K = any> {
8+
map = new Map<K, { lastSeen: [number, number]; result: T }>();
9+
10+
set(cacheKey: K, result: T): void {
11+
this.map.set(cacheKey, { lastSeen: process.hrtime(), result });
12+
log('setting entry for', cacheKey);
13+
}
14+
15+
get(cacheKey, settings = { lifetime: 10 /* seconds */ }): void | T {
16+
if (!this.map.has(cacheKey)) {
17+
log('cache miss for', cacheKey);
18+
return;
19+
}
20+
const { lastSeen, result } = this.map.get(cacheKey);
21+
// check freshness
22+
if (process.hrtime(lastSeen)[0] < settings.lifetime) {
23+
return result;
24+
}
25+
}
26+
}

packages/plugin/src/sibling-operations.ts renamed to packages/plugin/src/documents.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ import {
77
visit,
88
OperationTypeNode,
99
} from 'graphql';
10-
import { Source, asArray } from '@graphql-tools/utils';
10+
import { Source } from '@graphql-tools/utils';
1111
import { GraphQLProjectConfig } from 'graphql-config';
1212
import debugFactory from 'debug';
13-
import fastGlob from 'fast-glob';
13+
import fg from 'fast-glob';
1414
import { logger } from './utils';
15-
import type { Pointer } from './types';
15+
import { Pointer } from './types';
16+
import { ModuleCache } from './cache';
1617

1718
export type FragmentSource = { filePath: string; document: FragmentDefinitionNode };
1819
export type OperationSource = { filePath: string; document: OperationDefinitionNode };
@@ -50,12 +51,11 @@ const handleVirtualPath = (documents: Source[]): Source[] => {
5051
});
5152
};
5253

53-
const operationsCache = new Map<string, Source[]>();
54+
const operationsCache = new ModuleCache<Source[]>();
5455
const siblingOperationsCache = new Map<Source[], SiblingOperations>();
5556

5657
const getSiblings = (project: GraphQLProjectConfig): Source[] => {
57-
const documentsKey = asArray(project.documents).sort().join(',');
58-
58+
const documentsKey = project.documents;
5959
if (!documentsKey) {
6060
return [];
6161
}
@@ -70,9 +70,7 @@ const getSiblings = (project: GraphQLProjectConfig): Source[] => {
7070
});
7171
if (debug.enabled) {
7272
debug('Loaded %d operations', documents.length);
73-
const operationsPaths = fastGlob.sync(project.documents as Pointer, {
74-
absolute: true,
75-
});
73+
const operationsPaths = fg.sync(project.documents as Pointer, { absolute: true });
7674
debug('Operations pointers %O', operationsPaths);
7775
}
7876
siblings = handleVirtualPath(documents);
@@ -82,7 +80,7 @@ const getSiblings = (project: GraphQLProjectConfig): Source[] => {
8280
return siblings;
8381
};
8482

85-
export function getSiblingOperations(project: GraphQLProjectConfig): SiblingOperations {
83+
export function getDocuments(project: GraphQLProjectConfig): SiblingOperations {
8684
const siblings = getSiblings(project);
8785

8886
if (siblings.length === 0) {

packages/plugin/src/parser.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,38 @@ import debugFactory from 'debug';
44
import { convertToESTree, extractComments, extractTokens } from './estree-converter';
55
import { GraphQLESLintParseResult, ParserOptions } from './types';
66
import { getSchema } from './schema';
7-
import { getSiblingOperations } from './sibling-operations';
7+
import { getDocuments } from './documents';
88
import { loadGraphQLConfig } from './graphql-config';
9-
import { getOnDiskFilepath } from './utils';
9+
import { CWD, VIRTUAL_DOCUMENT_REGEX } from './utils';
1010

1111
const debug = debugFactory('graphql-eslint:parser');
1212

13-
debug('cwd %o', process.cwd());
13+
debug('cwd %o', CWD);
1414

1515
export function parseForESLint(code: string, options: ParserOptions): GraphQLESLintParseResult {
1616
try {
1717
const { filePath } = options;
18-
const realFilepath = getOnDiskFilepath(filePath);
19-
20-
const gqlConfig = loadGraphQLConfig(options);
21-
const projectForFile = realFilepath
22-
? gqlConfig.getProjectForFile(realFilepath)
23-
: gqlConfig.getDefault();
24-
25-
const schema = getSchema(projectForFile, options);
26-
const siblingOperations = getSiblingOperations(projectForFile);
27-
18+
// First parse code from file, in case of syntax error do not try load schema,
19+
// documents or even graphql-config instance
2820
const { document } = parseGraphQLSDL(filePath, code, {
2921
...options.graphQLParserOptions,
3022
noLocation: false,
3123
});
24+
const gqlConfig = loadGraphQLConfig(options);
25+
const realFilepath = filePath.replace(VIRTUAL_DOCUMENT_REGEX, '');
26+
const project = gqlConfig.getProjectForFile(realFilepath);
3227

33-
const comments = extractComments(document.loc);
34-
const tokens = extractTokens(filePath, code);
28+
const schema = getSchema(project, options.schemaOptions);
3529
const rootTree = convertToESTree(document, schema instanceof GraphQLSchema ? schema : null);
3630

3731
return {
3832
services: {
3933
schema,
40-
siblingOperations,
34+
siblingOperations: getDocuments(project),
4135
},
4236
ast: {
43-
comments,
44-
tokens,
37+
comments: extractComments(document.loc),
38+
tokens: extractTokens(filePath, code),
4539
loc: rootTree.loc,
4640
range: rootTree.range as [number, number],
4741
type: 'Program',

packages/plugin/src/rules/no-unused-fields.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { GraphQLSchema, TypeInfo, visit, visitWithTypeInfo } from 'graphql';
22
import { GraphQLESLintRule } from '../types';
33
import { requireGraphQLSchemaFromContext, requireSiblingsOperations } from '../utils';
4-
import { SiblingOperations } from '../sibling-operations';
4+
import { SiblingOperations } from '../documents';
55

66
const RULE_ID = 'no-unused-fields';
77

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import depthLimit from 'graphql-depth-limit';
44
import { DocumentNode, ExecutableDefinitionNode, GraphQLError, Kind } from 'graphql';
55
import { GraphQLESTreeNode } from '../estree-converter';
66
import { ARRAY_DEFAULT_OPTIONS, logger, requireSiblingsOperations } from '../utils';
7-
import { SiblingOperations } from '../sibling-operations';
7+
import { SiblingOperations } from '../documents';
88

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

packages/plugin/src/rules/unique-fragment-name.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { relative } from 'path';
22
import { ExecutableDefinitionNode, Kind } from 'graphql';
33
import { GraphQLESLintRule, GraphQLESLintRuleContext } from '../types';
44
import { GraphQLESTreeNode } from '../estree-converter';
5-
import { normalizePath, requireSiblingsOperations, getOnDiskFilepath } from '../utils';
6-
import { FragmentSource, OperationSource } from '../sibling-operations';
5+
import { normalizePath, requireSiblingsOperations, VIRTUAL_DOCUMENT_REGEX, CWD } from '../utils';
6+
import { FragmentSource, OperationSource } from '../documents';
77

88
const RULE_ID = 'unique-fragment-name';
99

@@ -32,7 +32,7 @@ export const checkNode = (
3232
data: {
3333
documentName,
3434
summary: conflictingDocuments
35-
.map(f => `\t${relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
35+
.map(f => `\t${relative(CWD, f.filePath.replace(VIRTUAL_DOCUMENT_REGEX, ''))}`)
3636
.join('\n'),
3737
},
3838
node: node.name,

packages/plugin/src/schema.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,48 @@
11
import { GraphQLSchema } from 'graphql';
22
import { GraphQLProjectConfig } from 'graphql-config';
3-
import { asArray } from '@graphql-tools/utils';
43
import debugFactory from 'debug';
5-
import fastGlob from 'fast-glob';
4+
import fg from 'fast-glob';
65
import chalk from 'chalk';
7-
import type { ParserOptions, Schema, Pointer } from './types';
6+
import { ParserOptions, Schema, Pointer } from './types';
7+
import { ModuleCache } from './cache';
88

9-
const schemaCache = new Map<string, GraphQLSchema | Error>();
9+
const schemaCache = new ModuleCache<GraphQLSchema | Error>();
1010
const debug = debugFactory('graphql-eslint:schema');
1111

1212
export function getSchema(
1313
project: GraphQLProjectConfig,
14-
options: Omit<ParserOptions, 'filePath'> = {},
14+
schemaOptions?: ParserOptions['schemaOptions'],
1515
): Schema {
16-
const schemaKey = asArray(project.schema).sort().join(',');
16+
const schemaKey = project.schema;
1717

1818
if (!schemaKey) {
1919
return null;
2020
}
2121

22-
if (schemaCache.has(schemaKey)) {
23-
return schemaCache.get(schemaKey);
22+
const cache = schemaCache.get(schemaKey);
23+
24+
if (cache) {
25+
return cache;
2426
}
2527

2628
let schema: Schema;
2729

2830
try {
2931
debug('Loading schema from %o', project.schema);
3032
schema = project.loadSchemaSync(project.schema, 'GraphQLSchema', {
31-
...options.schemaOptions,
33+
...schemaOptions,
3234
pluckConfig: project.extensions.pluckConfig,
3335
});
3436
if (debug.enabled) {
3537
debug('Schema loaded: %o', schema instanceof GraphQLSchema);
36-
const schemaPaths = fastGlob.sync(project.schema as Pointer, {
37-
absolute: true,
38-
});
38+
const schemaPaths = fg.sync(project.schema as Pointer, { absolute: true });
3939
debug('Schema pointers %O', schemaPaths);
4040
}
41+
// Do not set error to cache, since cache reload will be done after some `lifetime` seconds
42+
schemaCache.set(schemaKey, schema);
4143
} catch (error) {
4244
error.message = chalk.red(`Error while loading schema: ${error.message}`);
4345
schema = error as Error;
4446
}
45-
46-
schemaCache.set(schemaKey, schema);
4747
return schema;
4848
}

0 commit comments

Comments
 (0)