-
Notifications
You must be signed in to change notification settings - Fork 109
enhance(eslint-plugin): refactor the parts using tools #564
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@graphql-eslint/eslint-plugin': patch | ||
| --- | ||
|
|
||
| enhance(eslint-plugin): refactor the parts using tools |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { GraphQLSchema, TypeInfo, ASTKindToNode, Visitor, visit, visitWithTypeInfo, parse } from 'graphql'; | ||
| import { printSchemaWithDirectives } from '@graphql-tools/utils'; | ||
| import { GraphQLSchema, TypeInfo, ASTKindToNode, Visitor, visit, visitWithTypeInfo } from 'graphql'; | ||
| import { getDocumentNodeFromSchema, getRootTypeNames } from '@graphql-tools/utils'; | ||
| import { SiblingOperations } from './sibling-operations'; | ||
|
|
||
| export type ReachableTypes = Set<string>; | ||
|
|
@@ -12,10 +12,9 @@ export function getReachableTypes(schema: GraphQLSchema): ReachableTypes { | |
| if (process.env.NODE_ENV !== 'test' && reachableTypesCache) { | ||
| return reachableTypesCache; | ||
| } | ||
| // 👀 `printSchemaWithDirectives` keep all custom directives and `printSchema` from `graphql` not | ||
| const printedSchema = printSchemaWithDirectives(schema); // Returns a string representation of the schema | ||
| const astNode = parse(printedSchema); // Transforms the string into ASTNode | ||
| const cache = Object.create(null); | ||
|
|
||
| const astNode = getDocumentNodeFromSchema(schema); // Transforms the schema into ASTNode | ||
| const cache: Record<string, number> = Object.create(null); | ||
|
|
||
| const collect = (nodeType: any): void => { | ||
| let node = nodeType; | ||
|
|
@@ -32,10 +31,12 @@ export function getReachableTypes(schema: GraphQLSchema): ReachableTypes { | |
| node.operationTypes.forEach(collect); | ||
| }, | ||
| ObjectTypeDefinition(node) { | ||
| [node, ...node.interfaces].forEach(collect); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will reduce the number of iterations |
||
| collect(node); | ||
| node.interfaces?.forEach(collect); | ||
| }, | ||
| UnionTypeDefinition(node) { | ||
| [node, ...node.types].forEach(collect); | ||
| collect(node); | ||
| node.types?.forEach(collect); | ||
| }, | ||
| InputObjectTypeDefinition: collect, | ||
| InterfaceTypeDefinition: collect, | ||
|
|
@@ -49,7 +50,8 @@ export function getReachableTypes(schema: GraphQLSchema): ReachableTypes { | |
|
|
||
| visit(astNode, visitor); | ||
|
|
||
| const operationTypeNames = new Set(['Query', 'Mutation', 'Subscription']); | ||
| const operationTypeNames = getRootTypeNames(schema); | ||
|
|
||
| const usedTypes = Object.entries(cache) | ||
| .filter(([typeName, usedCount]) => usedCount > 1 || operationTypeNames.has(typeName)) | ||
| .map(([typeName]) => typeName); | ||
|
|
@@ -68,7 +70,7 @@ export function getUsedFields(schema: GraphQLSchema, operations: SiblingOperatio | |
| if (process.env.NODE_ENV !== 'test' && usedFieldsCache) { | ||
| return usedFieldsCache; | ||
| } | ||
| const usedFields: UsedFields = {}; | ||
| const usedFields: UsedFields = Object.create(null); | ||
| const typeInfo = new TypeInfo(schema); | ||
| const allDocuments = [...operations.getOperations(), ...operations.getFragments()]; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ import { GraphQLSchema } from 'graphql'; | |
| import { GraphQLConfig } from 'graphql-config'; | ||
| import { asArray } from '@graphql-tools/utils'; | ||
| import { ParserOptions } from './types'; | ||
| import { getOnDiskFilepath } from './utils'; | ||
| import { getOnDiskFilepath, loaderCache } from './utils'; | ||
|
|
||
| const schemaCache: Map<string, GraphQLSchema> = new Map(); | ||
|
|
||
|
|
@@ -17,12 +17,15 @@ export function getSchema(options: ParserOptions = {}, gqlConfig: GraphQLConfig) | |
| return null; | ||
| } | ||
|
|
||
| if (schemaCache.has(schemaKey)) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using |
||
| return schemaCache.get(schemaKey); | ||
| } | ||
| let schema = schemaCache.get(schemaKey); | ||
|
|
||
| const schema = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', options.schemaOptions); | ||
| schemaCache.set(schemaKey, schema); | ||
| if (!schema) { | ||
| schema = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', { | ||
| cache: loaderCache, | ||
| ...options.schemaOptions | ||
| }); | ||
| schemaCache.set(schemaKey, schema); | ||
| } | ||
|
|
||
| return schema; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,7 @@ import { | |
| import { Source, asArray } from '@graphql-tools/utils'; | ||
| import { GraphQLConfig } from 'graphql-config'; | ||
| import { ParserOptions } from './types'; | ||
| import { getOnDiskFilepath } from './utils'; | ||
| import { getOnDiskFilepath, loaderCache } from './utils'; | ||
|
|
||
| export type FragmentSource = { filePath: string; document: FragmentDefinitionNode }; | ||
| export type OperationSource = { filePath: string; document: OperationDefinitionNode }; | ||
|
|
@@ -61,15 +61,16 @@ const getSiblings = (filePath: string, gqlConfig: GraphQLConfig): Source[] => { | |
| return []; | ||
| } | ||
|
|
||
| if (operationsCache.has(documentsKey)) { | ||
| return operationsCache.get(documentsKey); | ||
| } | ||
| let siblings = operationsCache.get(documentsKey); | ||
|
|
||
| const documents = projectForFile.loadDocumentsSync(projectForFile.documents, { | ||
| skipGraphQLImport: true, | ||
| }); | ||
| const siblings = handleVirtualPath(documents) | ||
| operationsCache.set(documentsKey, siblings); | ||
| if (!siblings) { | ||
| const documents = projectForFile.loadDocumentsSync(projectForFile.documents, { | ||
| skipGraphQLImport: true, | ||
| cache: loaderCache | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might improve the performance a bit as well and remove the need of |
||
| }); | ||
| siblings = handleVirtualPath(documents) | ||
| operationsCache.set(documentsKey, siblings); | ||
| } | ||
|
|
||
| return siblings; | ||
| }; | ||
|
|
@@ -106,95 +107,95 @@ export function getSiblingOperations(options: ParserOptions, gqlConfig: GraphQLC | |
| // Since the siblings array is cached, we can use it as cache key. | ||
| // We should get the same array reference each time we get | ||
| // to this point for the same graphql project | ||
| if (siblingOperationsCache.has(siblings)) { | ||
| return siblingOperationsCache.get(siblings); | ||
| } | ||
|
|
||
| let fragmentsCache: FragmentSource[] | null = null; | ||
|
|
||
| const getFragments = (): FragmentSource[] => { | ||
| if (fragmentsCache === null) { | ||
| const result: FragmentSource[] = []; | ||
|
|
||
| for (const source of siblings) { | ||
| for (const definition of source.document.definitions || []) { | ||
| if (definition.kind === Kind.FRAGMENT_DEFINITION) { | ||
| result.push({ | ||
| filePath: source.location, | ||
| document: definition, | ||
| }); | ||
| let siblingOperations = siblingOperationsCache.get(siblings); | ||
| if (!siblingOperations) { | ||
| let fragmentsCache: FragmentSource[] | null = null; | ||
|
|
||
| const getFragments = (): FragmentSource[] => { | ||
| if (fragmentsCache === null) { | ||
| const result: FragmentSource[] = []; | ||
|
|
||
| for (const source of siblings) { | ||
| for (const definition of source.document.definitions || []) { | ||
| if (definition.kind === Kind.FRAGMENT_DEFINITION) { | ||
| result.push({ | ||
| filePath: source.location, | ||
| document: definition, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| fragmentsCache = result; | ||
| } | ||
| fragmentsCache = result; | ||
| } | ||
| return fragmentsCache; | ||
| }; | ||
|
|
||
| let cachedOperations: OperationSource[] | null = null; | ||
|
|
||
| const getOperations = (): OperationSource[] => { | ||
| if (cachedOperations === null) { | ||
| const result: OperationSource[] = []; | ||
|
|
||
| for (const source of siblings) { | ||
| for (const definition of source.document.definitions || []) { | ||
| if (definition.kind === Kind.OPERATION_DEFINITION) { | ||
| result.push({ | ||
| filePath: source.location, | ||
| document: definition, | ||
| }); | ||
| return fragmentsCache; | ||
| }; | ||
|
|
||
| let cachedOperations: OperationSource[] | null = null; | ||
|
|
||
| const getOperations = (): OperationSource[] => { | ||
| if (cachedOperations === null) { | ||
| const result: OperationSource[] = []; | ||
|
|
||
| for (const source of siblings) { | ||
| for (const definition of source.document.definitions || []) { | ||
| if (definition.kind === Kind.OPERATION_DEFINITION) { | ||
| result.push({ | ||
| filePath: source.location, | ||
| document: definition, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| cachedOperations = result; | ||
| } | ||
| cachedOperations = result; | ||
| } | ||
| return cachedOperations; | ||
| }; | ||
|
|
||
| const getFragment = (name: string) => getFragments().filter(f => f.document.name?.value === name); | ||
|
|
||
| const collectFragments = ( | ||
| selectable: SelectionSetNode | OperationDefinitionNode | FragmentDefinitionNode, | ||
| recursive = true, | ||
| collected: Map<string, FragmentDefinitionNode> = new Map() | ||
| ) => { | ||
| visit(selectable, { | ||
| FragmentSpread(spread: FragmentSpreadNode) { | ||
| const name = spread.name.value; | ||
| const fragmentInfo = getFragment(name); | ||
|
|
||
| if (fragmentInfo.length === 0) { | ||
| // eslint-disable-next-line no-console | ||
| console.warn( | ||
| `Unable to locate fragment named "${name}", please make sure it's loaded using "parserOptions.operations"` | ||
| ); | ||
| return; | ||
| } | ||
| const fragment = fragmentInfo[0]; | ||
| const alreadyVisited = collected.has(name); | ||
| return cachedOperations; | ||
| }; | ||
|
|
||
| if (!alreadyVisited) { | ||
| collected.set(name, fragment.document); | ||
| if (recursive) { | ||
| collectFragments(fragment.document, recursive, collected); | ||
| const getFragment = (name: string) => getFragments().filter(f => f.document.name?.value === name); | ||
|
|
||
| const collectFragments = ( | ||
| selectable: SelectionSetNode | OperationDefinitionNode | FragmentDefinitionNode, | ||
| recursive = true, | ||
| collected: Map<string, FragmentDefinitionNode> = new Map() | ||
| ) => { | ||
| visit(selectable, { | ||
| FragmentSpread(spread: FragmentSpreadNode) { | ||
| const name = spread.name.value; | ||
| const fragmentInfo = getFragment(name); | ||
|
|
||
| if (fragmentInfo.length === 0) { | ||
| // eslint-disable-next-line no-console | ||
| console.warn( | ||
| `Unable to locate fragment named "${name}", please make sure it's loaded using "parserOptions.operations"` | ||
| ); | ||
| return; | ||
| } | ||
| } | ||
| }, | ||
| }); | ||
| return collected; | ||
| }; | ||
|
|
||
| const siblingOperations: SiblingOperations = { | ||
| available: true, | ||
| getFragments, | ||
| getOperations, | ||
| getFragment, | ||
| getFragmentByType: typeName => getFragments().filter(f => f.document.typeCondition?.name?.value === typeName), | ||
| getOperation: name => getOperations().filter(o => o.document.name?.value === name), | ||
| getOperationByType: type => getOperations().filter(o => o.document.operation === type), | ||
| getFragmentsInUse: (selectable, recursive = true) => Array.from(collectFragments(selectable, recursive).values()), | ||
| }; | ||
| siblingOperationsCache.set(siblings, siblingOperations); | ||
| const fragment = fragmentInfo[0]; | ||
| const alreadyVisited = collected.has(name); | ||
|
|
||
| if (!alreadyVisited) { | ||
| collected.set(name, fragment.document); | ||
| if (recursive) { | ||
| collectFragments(fragment.document, recursive, collected); | ||
| } | ||
| } | ||
| }, | ||
| }); | ||
| return collected; | ||
| }; | ||
|
|
||
| siblingOperations = { | ||
| available: true, | ||
| getFragments, | ||
| getOperations, | ||
| getFragment, | ||
| getFragmentByType: typeName => getFragments().filter(f => f.document.typeCondition?.name?.value === typeName), | ||
| getOperation: name => getOperations().filter(o => o.document.name?.value === name), | ||
| getOperationByType: type => getOperations().filter(o => o.document.operation === type), | ||
| getFragmentsInUse: (selectable, recursive = true) => Array.from(collectFragments(selectable, recursive).values()), | ||
| }; | ||
|
|
||
| siblingOperationsCache.set(siblings, siblingOperations); | ||
| } | ||
| return siblingOperations; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
printSchemaWithDirectivesalso uses this function and no need to doprintandparseagain.