From fa9cc10a1d350887e60e416cb41770f3685d6a3b Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 14 Feb 2022 13:15:37 +0100 Subject: [PATCH] reorder module, drop no-use-before-define disable --- packages/gatsby/src/schema/print.ts | 638 ++++++++++++++-------------- 1 file changed, 318 insertions(+), 320 deletions(-) diff --git a/packages/gatsby/src/schema/print.ts b/packages/gatsby/src/schema/print.ts index 2e10a2b612fd2..dc9125f750e1a 100644 --- a/packages/gatsby/src/schema/print.ts +++ b/packages/gatsby/src/schema/print.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-use-before-define */ - import * as fs from "fs-extra" import { EnumTypeComposer, @@ -44,193 +42,186 @@ export interface ISchemaPrintConfig { rewrite?: boolean } -export const printTypeDefinitions = ({ - config, - schemaComposer, -}: { - config: ISchemaPrintConfig - schemaComposer: SchemaComposer -}): Promise => { - if (!config) return Promise.resolve() +const breakLine = (line: string, maxLen: number): Array => { + const parts = line.split(new RegExp(`((?: |^).{15,${maxLen - 40}}(?= |$))`)) + if (parts.length < 4) { + return [line] + } + const sublines = [parts[0] + parts[1] + parts[2]] + for (let i = 3; i < parts.length; i += 2) { + sublines.push(parts[i].slice(1) + parts[i + 1]) + } + return sublines +} - const { - path, - include, - exclude, - withFieldTypes, - rewrite = false, - } = config || {} +const descriptionLines = ( + description: string, + maxLen: number +): Array => { + const rawLines = description.split(`\n`) + return _.flatMap(rawLines, line => { + if (line.length < maxLen + 5) { + return line + } + // For > 120 character long lines, cut at space boundaries into sublines + // of ~80 chars. + return breakLine(line, maxLen) + }) +} - if (!path) { - report.error( - `Printing type definitions aborted. Please provide a file path.` - ) - return Promise.resolve() - } +const printBlock = (items: Array): string => + items.length !== 0 ? ` {\n` + items.join(`\n`) + `\n}` : `` - if (!rewrite && fs.existsSync(path)) { - report.error( - `Printing type definitions aborted. The file \`${path}\` already exists.` - ) - return Promise.resolve() +const printDeprecated = ( + fieldOrEnumVal: + | ObjectTypeComposerFieldConfig + | EnumTypeComposerValueConfig +): string => { + const reason = fieldOrEnumVal.deprecationReason + if (!reason) { + return `` } + const reasonAST = astFromValue(reason, GraphQLString) + if (reasonAST && reason !== `` && reason !== DEFAULT_DEPRECATION_REASON) { + return ` @deprecated(reason: ` + print(reasonAST) + `)` + } + return ` @deprecated` +} - const internalTypes = [ - `Boolean`, - `Buffer`, - `Date`, - `Float`, - `ID`, - `Int`, - `Internal`, - `InternalInput`, - `JSON`, - `Json`, - `Node`, - `NodeInput`, - `Query`, - `String`, - ] - const internalPlugins = [`internal-data-bridge`] - - const typesToExclude = exclude?.types || [] - const pluginsToExclude = exclude?.plugins || [] +const printDescription = ( + def: + | ObjectTypeComposerFieldConfig + | NamedTypeComposer + | ObjectTypeComposerArgumentConfig + | EnumTypeComposerValueConfig, + indentation = ``, + firstInBlock = true +): string => { + const description = isNamedTypeComposer(def) + ? def.getDescription() + : def.description + if (!description) { + return `` + } - const getName = (tc: NamedTypeComposer): string => tc.getTypeName() + const lines = descriptionLines(description, 120 - indentation.length) - const isInternalType = (tc: NamedTypeComposer): boolean => { - const typeName = getName(tc) - if (internalTypes.includes(typeName)) { - return true - } + const text = lines.join(`\n`) + const preferMultipleLines = text.length > 70 + const blockString = printBlockString(text, ``, preferMultipleLines) + const prefix = indentation && !firstInBlock ? `\n` + indentation : indentation - const plugin = tc.getExtension(`plugin`) - if (typeof plugin === `string` && internalPlugins.includes(plugin)) { - return true - } + return prefix + blockString.replace(/\n/g, `\n` + indentation) + `\n` +} - return false +const printDirectiveArgs = (args: any, directive: GraphQLDirective): string => { + if (!args || !directive) { + return `` } - const shouldIncludeType = (tc: NamedTypeComposer): boolean => { - const typeName = getName(tc) - if (typesToExclude.includes(typeName)) { - return false - } - if (include?.types && !include.types.includes(typeName)) { - return false - } + const directiveArgs = Object.entries(args) + if (directiveArgs.length === 0) { + return `` + } - const plugin = tc.getExtension(`plugin`) - if (typeof plugin === `string` && pluginsToExclude.includes(plugin)) { - return false - } - if ( - typeof plugin === `string` && - include?.plugins && - !include.plugins.includes(plugin) - ) { - return false - } + return ( + `(` + + directiveArgs + .map(([name, value]) => { + const arg = + directive.args && directive.args.find(arg => arg.name === name) - return true - } + return arg && `${name}: ${print(astFromValue(value, arg.type)!)}` + }) + .join(`, `) + + `)` + ) +} - // Save processed type names, not references to the type composers, - // because of how graphql-compose, at least in v6, processes - // inline types - const processedTypes = new Set() - const typeDefs = new Set>() +export const printDirectives = ( + extensions: Extensions, + directives: Array +): string => + Object.entries(extensions) + .map(([name, args]) => { + if ([...internalExtensionNames, `deprecated`].includes(name)) return `` + return ( + ` @${name}` + + printDirectiveArgs( + args, + directives.find(directive => directive.name === name)! + ) + ) + }) + .join(``) - const addType = ( - tc: NamedTypeComposer - ): null | Set> => { - const typeName = getName(tc) - if (!processedTypes.has(typeName) && !isInternalType(tc)) { - processedTypes.add(typeName) - return typeDefs.add(tc) +const printInputValue = ([name, inputTC]: [ + string, + ObjectTypeComposerArgumentConfig +]): string => { + let argDecl = name + `: ` + inputTC.type.getTypeName() + if (inputTC.defaultValue) { + const defaultAST = astFromValue( + inputTC.defaultValue, + inputTC.type.getType() + ) + if (defaultAST) { + argDecl += ` = ${print(defaultAST)}` } - processedTypes.add(typeName) - return null } + return argDecl +} - const addWithFieldTypes = (tc: NamedTypeComposer): void => { - if ( - addType(tc) && - (tc instanceof ObjectTypeComposer || - tc instanceof InterfaceTypeComposer || - tc instanceof InputTypeComposer) - ) { - if (tc instanceof ObjectTypeComposer) { - const interfaces = tc.getInterfaces() - interfaces.forEach(iface => { - const ifaceName = iface.getTypeName() - if (ifaceName !== `Node`) { - addWithFieldTypes(schemaComposer.getAnyTC(ifaceName)) - } - }) - } - - tc.getFieldNames().forEach(fieldName => { - const fieldType = tc.getFieldTC(fieldName) - addWithFieldTypes(fieldType) +const printArgs = ( + args: ObjectTypeComposerArgumentConfigMap | undefined, + indentation = `` +): string => { + if (!args) { + return `` + } + const argsArray = Object.entries(args) + if (argsArray.length === 0) { + return `` + } - if (!(tc instanceof InputTypeComposer)) { - const fieldArgs = tc.getFieldArgs(fieldName) - Object.keys(fieldArgs).forEach(argName => { - try { - addWithFieldTypes(tc.getFieldArgTC(fieldName, argName)) - } catch { - // this type might not exist yet. If it won't be created by the end - // of schema creation then building schema will fail and fact that we - // skip it here won't matter - } - }) - } - }) - } + // If all args have no description, print them on one line + if (argsArray.every(([_name, argTC]) => !argTC.description)) { + return `(` + argsArray.map(printInputValue).join(`, `) + `)` } - schemaComposer.forEach(tc => { - if (!isInternalType(tc) && shouldIncludeType(tc)) { - if (withFieldTypes) { - addWithFieldTypes(tc) - } else { - addType(tc) - } - } - }) - - const printedTypeDefs = [ - `### Type definitions saved at ${new Date().toISOString()} ###`, - ] - - try { - typeDefs.forEach(tc => printedTypeDefs.push(printType(tc))) - report.info(`Writing GraphQL type definitions to ${path}`) - return fs.writeFile(path, printedTypeDefs.join(`\n\n`)) - } catch (error) { - report.error(`Failed writing type definitions to \`${path}\`.`, error) - return Promise.resolve() - } + return ( + `(\n` + + argsArray + .map( + ([_name, argTC], i) => + printDescription(argTC, ` ` + indentation, !i) + + ` ` + + indentation + + printInputValue([_name, argTC]) + ) + .join(`\n`) + + `\n` + + indentation + + `)` + ) } -const printType = (tc: NamedTypeComposer): string => { - if (tc instanceof ObjectTypeComposer) { - return printObjectType(tc) - } else if (tc instanceof InterfaceTypeComposer) { - return printInterfaceType(tc) - } else if (tc instanceof UnionTypeComposer) { - return printUnionType(tc) - } else if (tc instanceof EnumTypeComposer) { - return printEnumType(tc) - } else if (tc instanceof ScalarTypeComposer) { - return printScalarType(tc) - } else if (tc instanceof InputTypeComposer) { - return printInputObjectType(tc) - } - - return `` +const printFields = ( + fields: ObjectTypeComposerFieldConfigMap, + directives: Array +): string => { + const printedFields = Object.entries(fields).map( + ([fieldName, fieldTC], i) => + printDescription(fieldTC, ` `, !i) + + ` ` + + fieldName + + printArgs(fieldTC.args, ` `) + + `: ` + + String(fieldTC.type.getTypeName()) + + printDirectives(fieldTC.extensions || {}, directives) + + printDeprecated(fieldTC) + ) + return printBlock(printedFields) } const printScalarType = (tc: ScalarTypeComposer): string => @@ -301,184 +292,191 @@ const printInputObjectType = (tc: InputTypeComposer): string => { return printDescription(tc) + `input ${tc.getTypeName()}` + printBlock(fields) } -const printFields = ( - fields: ObjectTypeComposerFieldConfigMap, - directives: Array -): string => { - const printedFields = Object.entries(fields).map( - ([fieldName, fieldTC], i) => - printDescription(fieldTC, ` `, !i) + - ` ` + - fieldName + - printArgs(fieldTC.args, ` `) + - `: ` + - String(fieldTC.type.getTypeName()) + - printDirectives(fieldTC.extensions || {}, directives) + - printDeprecated(fieldTC) - ) - return printBlock(printedFields) +const printType = (tc: NamedTypeComposer): string => { + if (tc instanceof ObjectTypeComposer) { + return printObjectType(tc) + } else if (tc instanceof InterfaceTypeComposer) { + return printInterfaceType(tc) + } else if (tc instanceof UnionTypeComposer) { + return printUnionType(tc) + } else if (tc instanceof EnumTypeComposer) { + return printEnumType(tc) + } else if (tc instanceof ScalarTypeComposer) { + return printScalarType(tc) + } else if (tc instanceof InputTypeComposer) { + return printInputObjectType(tc) + } + + return `` } -const printBlock = (items: Array): string => - items.length !== 0 ? ` {\n` + items.join(`\n`) + `\n}` : `` +export const printTypeDefinitions = ({ + config, + schemaComposer, +}: { + config: ISchemaPrintConfig + schemaComposer: SchemaComposer +}): Promise => { + if (!config) return Promise.resolve() -const printArgs = ( - args: ObjectTypeComposerArgumentConfigMap | undefined, - indentation = `` -): string => { - if (!args) { - return `` - } - const argsArray = Object.entries(args) - if (argsArray.length === 0) { - return `` - } + const { + path, + include, + exclude, + withFieldTypes, + rewrite = false, + } = config || {} - // If all args have no description, print them on one line - if (argsArray.every(([_name, argTC]) => !argTC.description)) { - return `(` + argsArray.map(printInputValue).join(`, `) + `)` + if (!path) { + report.error( + `Printing type definitions aborted. Please provide a file path.` + ) + return Promise.resolve() } - return ( - `(\n` + - argsArray - .map( - ([_name, argTC], i) => - printDescription(argTC, ` ` + indentation, !i) + - ` ` + - indentation + - printInputValue([_name, argTC]) - ) - .join(`\n`) + - `\n` + - indentation + - `)` - ) -} - -const printInputValue = ([name, inputTC]: [ - string, - ObjectTypeComposerArgumentConfig -]): string => { - let argDecl = name + `: ` + inputTC.type.getTypeName() - if (inputTC.defaultValue) { - const defaultAST = astFromValue( - inputTC.defaultValue, - inputTC.type.getType() + if (!rewrite && fs.existsSync(path)) { + report.error( + `Printing type definitions aborted. The file \`${path}\` already exists.` ) - if (defaultAST) { - argDecl += ` = ${print(defaultAST)}` - } + return Promise.resolve() } - return argDecl -} -export const printDirectives = ( - extensions: Extensions, - directives: Array -): string => - Object.entries(extensions) - .map(([name, args]) => { - if ([...internalExtensionNames, `deprecated`].includes(name)) return `` - return ( - ` @${name}` + - printDirectiveArgs( - args, - directives.find(directive => directive.name === name)! - ) - ) - }) - .join(``) + const internalTypes = [ + `Boolean`, + `Buffer`, + `Date`, + `Float`, + `ID`, + `Int`, + `Internal`, + `InternalInput`, + `JSON`, + `Json`, + `Node`, + `NodeInput`, + `Query`, + `String`, + ] + const internalPlugins = [`internal-data-bridge`] -const printDirectiveArgs = (args: any, directive: GraphQLDirective): string => { - if (!args || !directive) { - return `` - } + const typesToExclude = exclude?.types || [] + const pluginsToExclude = exclude?.plugins || [] - const directiveArgs = Object.entries(args) - if (directiveArgs.length === 0) { - return `` - } + const getName = (tc: NamedTypeComposer): string => tc.getTypeName() - return ( - `(` + - directiveArgs - .map(([name, value]) => { - const arg = - directive.args && directive.args.find(arg => arg.name === name) + const isInternalType = (tc: NamedTypeComposer): boolean => { + const typeName = getName(tc) + if (internalTypes.includes(typeName)) { + return true + } - return arg && `${name}: ${print(astFromValue(value, arg.type)!)}` - }) - .join(`, `) + - `)` - ) -} + const plugin = tc.getExtension(`plugin`) + if (typeof plugin === `string` && internalPlugins.includes(plugin)) { + return true + } -const printDeprecated = ( - fieldOrEnumVal: - | ObjectTypeComposerFieldConfig - | EnumTypeComposerValueConfig -): string => { - const reason = fieldOrEnumVal.deprecationReason - if (!reason) { - return `` + return false } - const reasonAST = astFromValue(reason, GraphQLString) - if (reasonAST && reason !== `` && reason !== DEFAULT_DEPRECATION_REASON) { - return ` @deprecated(reason: ` + print(reasonAST) + `)` + + const shouldIncludeType = (tc: NamedTypeComposer): boolean => { + const typeName = getName(tc) + if (typesToExclude.includes(typeName)) { + return false + } + if (include?.types && !include.types.includes(typeName)) { + return false + } + + const plugin = tc.getExtension(`plugin`) + if (typeof plugin === `string` && pluginsToExclude.includes(plugin)) { + return false + } + if ( + typeof plugin === `string` && + include?.plugins && + !include.plugins.includes(plugin) + ) { + return false + } + + return true } - return ` @deprecated` -} -const printDescription = ( - def: - | ObjectTypeComposerFieldConfig - | NamedTypeComposer - | ObjectTypeComposerArgumentConfig - | EnumTypeComposerValueConfig, - indentation = ``, - firstInBlock = true -): string => { - const description = isNamedTypeComposer(def) - ? def.getDescription() - : def.description - if (!description) { - return `` + // Save processed type names, not references to the type composers, + // because of how graphql-compose, at least in v6, processes + // inline types + const processedTypes = new Set() + const typeDefs = new Set>() + + const addType = ( + tc: NamedTypeComposer + ): null | Set> => { + const typeName = getName(tc) + if (!processedTypes.has(typeName) && !isInternalType(tc)) { + processedTypes.add(typeName) + return typeDefs.add(tc) + } + processedTypes.add(typeName) + return null } - const lines = descriptionLines(description, 120 - indentation.length) + const addWithFieldTypes = (tc: NamedTypeComposer): void => { + if ( + addType(tc) && + (tc instanceof ObjectTypeComposer || + tc instanceof InterfaceTypeComposer || + tc instanceof InputTypeComposer) + ) { + if (tc instanceof ObjectTypeComposer) { + const interfaces = tc.getInterfaces() + interfaces.forEach(iface => { + const ifaceName = iface.getTypeName() + if (ifaceName !== `Node`) { + addWithFieldTypes(schemaComposer.getAnyTC(ifaceName)) + } + }) + } - const text = lines.join(`\n`) - const preferMultipleLines = text.length > 70 - const blockString = printBlockString(text, ``, preferMultipleLines) - const prefix = indentation && !firstInBlock ? `\n` + indentation : indentation + tc.getFieldNames().forEach(fieldName => { + const fieldType = tc.getFieldTC(fieldName) + addWithFieldTypes(fieldType) - return prefix + blockString.replace(/\n/g, `\n` + indentation) + `\n` -} + if (!(tc instanceof InputTypeComposer)) { + const fieldArgs = tc.getFieldArgs(fieldName) + Object.keys(fieldArgs).forEach(argName => { + try { + addWithFieldTypes(tc.getFieldArgTC(fieldName, argName)) + } catch { + // this type might not exist yet. If it won't be created by the end + // of schema creation then building schema will fail and fact that we + // skip it here won't matter + } + }) + } + }) + } + } -const descriptionLines = ( - description: string, - maxLen: number -): Array => { - const rawLines = description.split(`\n`) - return _.flatMap(rawLines, line => { - if (line.length < maxLen + 5) { - return line + schemaComposer.forEach(tc => { + if (!isInternalType(tc) && shouldIncludeType(tc)) { + if (withFieldTypes) { + addWithFieldTypes(tc) + } else { + addType(tc) + } } - // For > 120 character long lines, cut at space boundaries into sublines - // of ~80 chars. - return breakLine(line, maxLen) }) -} -const breakLine = (line: string, maxLen: number): Array => { - const parts = line.split(new RegExp(`((?: |^).{15,${maxLen - 40}}(?= |$))`)) - if (parts.length < 4) { - return [line] - } - const sublines = [parts[0] + parts[1] + parts[2]] - for (let i = 3; i < parts.length; i += 2) { - sublines.push(parts[i].slice(1) + parts[i + 1]) + const printedTypeDefs = [ + `### Type definitions saved at ${new Date().toISOString()} ###`, + ] + + try { + typeDefs.forEach(tc => printedTypeDefs.push(printType(tc))) + report.info(`Writing GraphQL type definitions to ${path}`) + return fs.writeFile(path, printedTypeDefs.join(`\n\n`)) + } catch (error) { + report.error(`Failed writing type definitions to \`${path}\`.`, error) + return Promise.resolve() } - return sublines }