graphql#FieldDefinitionNode TypeScript Examples

The following examples show how to use graphql#FieldDefinitionNode. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: generate-query.ts    From graphql-query-generator with MIT License 6 votes vote down vote up
function getTypeNameMetaFieldDef(): FieldDefinitionNode {
  return {
    name: {
      kind: 'Name',
      value: '__typename'
    },
    kind: 'FieldDefinition',
    type: {
      kind: 'NonNullType',
      type: {
        kind: 'NamedType',
        name: {
          kind: 'Name',
          value: 'String'
        }
      }
    }
  }
}
Example #2
Source File: generate-query.ts    From graphql-query-generator with MIT License 6 votes vote down vote up
function isInterfaceField(
  field: FieldDefinitionNode,
  schema: GraphQLSchema
): boolean {
  const ast = schema.getType(getTypeName(field.type)).astNode
  return (
    typeof ast !== 'undefined' && ast.kind === Kind.INTERFACE_TYPE_DEFINITION
  )
}
Example #3
Source File: generate-query.ts    From graphql-query-generator with MIT License 6 votes vote down vote up
function fieldHasLeafs(
  field: FieldDefinitionNode,
  schema: GraphQLSchema
): boolean {
  const ast = schema.getType(getTypeName(field.type)).astNode
  if (
    ast.kind === Kind.OBJECT_TYPE_DEFINITION ||
    ast.kind === Kind.INTERFACE_TYPE_DEFINITION
  ) {
    return ast.fields.some((child) => {
      const childAst = schema.getType(getTypeName(child.type)).astNode
      return (
        typeof childAst === 'undefined' ||
        childAst.kind === Kind.SCALAR_TYPE_DEFINITION
      )
    })
  } else if (ast.kind === Kind.UNION_TYPE_DEFINITION) {
    return ast.types.some((child) => {
      let unionNamedTypes = (schema.getType(
        child.name.value
      ) as GraphQLObjectType).astNode.fields

      return unionNamedTypes.some((child) => {
        const childAst = schema.getType(getTypeName(child.type)).astNode
        return (
          typeof childAst === 'undefined' ||
          childAst.kind === Kind.SCALAR_TYPE_DEFINITION
        )
      })
    })
  }

  return false
}
Example #4
Source File: ttl-transformer.ts    From graphql-ttl-transformer with MIT License 6 votes vote down vote up
public field = (
    parent: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode,
    definition: FieldDefinitionNode,
    directive: DirectiveNode,
    acc: TransformerContext
  ) => {
    if (!["AWSTimestamp", "Int"].includes(getBaseType(definition.type))) {
      throw new InvalidDirectiveError(
        'Directive "ttl" must be used only on AWSTimestamp or Int type fields.'
      );
    }

    let numberOfTtlDirectivesInsideParentType = 0;
    if (parent.fields) {
      parent.fields.forEach((field) => {
        if (field.directives) {
          numberOfTtlDirectivesInsideParentType += field.directives.filter(
            (directive) => directive.name.value === "ttl"
          ).length;
        }
      });
    }
    if (numberOfTtlDirectivesInsideParentType > 1) {
      throw new InvalidDirectiveError(
        'Directive "ttl" must be used only once in the same type.'
      );
    }

    const tableName = ModelResourceIDs.ModelTableResourceID(parent.name.value);
    const table = acc.getResource(tableName);
    const fieldName = definition.name.value;
    table.Properties = {
      ...table.Properties,
      TimeToLiveSpecification: {
        AttributeName: fieldName,
        Enabled: true,
      },
    };
  };
Example #5
Source File: generate-query.ts    From graphql-query-generator with MIT License 5 votes vote down vote up
function isObjectField(
  field: FieldDefinitionNode,
  schema: GraphQLSchema
): boolean {
  const ast = schema.getType(getTypeName(field.type)).astNode
  return typeof ast !== 'undefined' && ast.kind === Kind.OBJECT_TYPE_DEFINITION
}
Example #6
Source File: generate-query.ts    From graphql-query-generator with MIT License 5 votes vote down vote up
function isUnionField(
  field: FieldDefinitionNode,
  schema: GraphQLSchema
): boolean {
  const ast = schema.getType(getTypeName(field.type)).astNode
  return typeof ast !== 'undefined' && ast.kind === Kind.UNION_TYPE_DEFINITION
}
Example #7
Source File: generate-query.ts    From graphql-query-generator with MIT License 5 votes vote down vote up
/**
 * Returns the first slicing argument defined in the field's @listSize
 * directive, if:
 *  - The @listSize directive is indeed present, and defines slicing arguments
 *  - The requiredArguments do not already include any of the defined slicing
 *    arguments
 *  - The @listSize directive doesn't also set requireOneSlicingArgument to
 *    false
 *
 * TODO: add link to specification / documentation of @listSize directive
 */
function getMissingSlicingArg(
  requiredArguments: InputValueDefinitionNode[],
  field: FieldDefinitionNode,
  schema: GraphQLSchema
): InputValueDefinitionNode {
  // Return null if there is no @listSize directive:
  const listSizeDirective = getListSizeDirective(field)
  if (!listSizeDirective) return null

  // Return null if @listSize directive defines no slicing arguments:
  const slicingArgumentsArg = getSlicingArguments(listSizeDirective)
  if (!slicingArgumentsArg) return null

  // Return null if requireOneSlicingArgument is set to false:
  const requireOneSlicingArg = getRequireOneSlicingArgument(listSizeDirective)
  if (
    requireOneSlicingArg &&
    (requireOneSlicingArg.value as BooleanValueNode).value === false // already checked requireOneSlicingArg is a boolean
  )
    return null

  // Return null if a slicing argument is already used:
  const slicingArguments = (slicingArgumentsArg.value as ListValueNode).values // already checked slicingArgumentsArg is a list
    .filter((value) => value.kind === 'StringValue')
    .map((value) => (value as StringValueNode).value)
  const usesSlicingArg = slicingArguments.some((slicingArg) =>
    requiredArguments
      .map((existing) => existing.name.value)
      .includes(slicingArg)
  )
  if (usesSlicingArg) return null

  // Returns the first slicing argument if slicing argument can be split on '.'
  // then we just need to check the first level match
  return field.arguments.find((arg) => {
    return slicingArguments.find((slicingArgument) => {
      const tokenizedPath = slicingArgument.split('.')
      if (tokenizedPath.length < 1) return false

      return arg.name.value === tokenizedPath[0]
    })
  })
}
Example #8
Source File: generate-query.ts    From graphql-query-generator with MIT License 5 votes vote down vote up
/**
 * Given a field node, return the listSize directive, if it exists
 */
function getListSizeDirective(field: FieldDefinitionNode): DirectiveNode {
  return field?.directives.find((dir) => dir.name.value === 'listSize')
}
Example #9
Source File: appsync-visitor.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
FieldDefinition(node: FieldDefinitionNode): CodeGenField {
    const directive = this.getDirectives(node.directives);
    return {
      name: node.name.value,
      directives: directive,
      ...getTypeInfo(node.type, this._schema),
    };
  }
Example #10
Source File: alphabetize.ts    From graphql-eslint with MIT License 4 votes vote down vote up
rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
  meta: {
    type: 'suggestion',
    fixable: 'code',
    docs: {
      category: ['Schema', 'Operations'],
      description:
        'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
      url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
      examples: [
        {
          title: 'Incorrect',
          usage: [{ fields: [Kind.OBJECT_TYPE_DEFINITION] }],
          code: /* GraphQL */ `
            type User {
              password: String
              firstName: String! # should be before "password"
              age: Int # should be before "firstName"
              lastName: String!
            }
          `,
        },
        {
          title: 'Correct',
          usage: [{ fields: [Kind.OBJECT_TYPE_DEFINITION] }],
          code: /* GraphQL */ `
            type User {
              age: Int
              firstName: String!
              lastName: String!
              password: String
            }
          `,
        },
        {
          title: 'Incorrect',
          usage: [{ values: [Kind.ENUM_TYPE_DEFINITION] }],
          code: /* GraphQL */ `
            enum Role {
              SUPER_ADMIN
              ADMIN # should be before "SUPER_ADMIN"
              USER
              GOD # should be before "USER"
            }
          `,
        },
        {
          title: 'Correct',
          usage: [{ values: [Kind.ENUM_TYPE_DEFINITION] }],
          code: /* GraphQL */ `
            enum Role {
              ADMIN
              GOD
              SUPER_ADMIN
              USER
            }
          `,
        },
        {
          title: 'Incorrect',
          usage: [{ selections: [Kind.OPERATION_DEFINITION] }],
          code: /* GraphQL */ `
            query {
              me {
                firstName
                lastName
                email # should be before "lastName"
              }
            }
          `,
        },
        {
          title: 'Correct',
          usage: [{ selections: [Kind.OPERATION_DEFINITION] }],
          code: /* GraphQL */ `
            query {
              me {
                email
                firstName
                lastName
              }
            }
          `,
        },
      ],
      configOptions: {
        schema: [
          {
            fields: fieldsEnum,
            values: valuesEnum,
            arguments: argumentsEnum,
            // TODO: add in graphql-eslint v4
            // definitions: true,
          },
        ],
        operations: [
          {
            selections: selectionsEnum,
            variables: variablesEnum,
            arguments: [Kind.FIELD, Kind.DIRECTIVE],
          },
        ],
      },
    },
    messages: {
      [RULE_ID]: '`{{ currName }}` should be before {{ prevName }}.',
    },
    schema: {
      type: 'array',
      minItems: 1,
      maxItems: 1,
      items: {
        type: 'object',
        additionalProperties: false,
        minProperties: 1,
        properties: {
          fields: {
            ...ARRAY_DEFAULT_OPTIONS,
            items: {
              enum: fieldsEnum,
            },
            description: 'Fields of `type`, `interface`, and `input`.',
          },
          values: {
            ...ARRAY_DEFAULT_OPTIONS,
            items: {
              enum: valuesEnum,
            },
            description: 'Values of `enum`.',
          },
          selections: {
            ...ARRAY_DEFAULT_OPTIONS,
            items: {
              enum: selectionsEnum,
            },
            description: 'Selections of `fragment` and operations `query`, `mutation` and `subscription`.',
          },
          variables: {
            ...ARRAY_DEFAULT_OPTIONS,
            items: {
              enum: variablesEnum,
            },
            description: 'Variables of operations `query`, `mutation` and `subscription`.',
          },
          arguments: {
            ...ARRAY_DEFAULT_OPTIONS,
            items: {
              enum: argumentsEnum,
            },
            description: 'Arguments of fields and directives.',
          },
          definitions: {
            type: 'boolean',
            description: 'Definitions – `type`, `interface`, `enum`, `scalar`, `input`, `union` and `directive`.',
            default: false,
          },
        },
      },
    },
  },
  create(context) {
    const sourceCode = context.getSourceCode();

    function isNodeAndCommentOnSameLine(node: { loc: SourceLocation }, comment: Comment): boolean {
      return node.loc.end.line === comment.loc.start.line;
    }

    function getBeforeComments(node): Comment[] {
      const commentsBefore = sourceCode.getCommentsBefore(node);
      if (commentsBefore.length === 0) {
        return [];
      }
      const tokenBefore = sourceCode.getTokenBefore(node);
      if (tokenBefore) {
        return commentsBefore.filter(comment => !isNodeAndCommentOnSameLine(tokenBefore, comment));
      }
      const filteredComments = [];
      const nodeLine = node.loc.start.line;
      // Break on comment that not attached to node
      for (let i = commentsBefore.length - 1; i >= 0; i -= 1) {
        const comment = commentsBefore[i];
        if (nodeLine - comment.loc.start.line - filteredComments.length > 1) {
          break;
        }
        filteredComments.unshift(comment);
      }
      return filteredComments;
    }

    function getRangeWithComments(node): AST.Range {
      if (node.kind === Kind.VARIABLE) {
        node = node.parent;
      }
      const [firstBeforeComment] = getBeforeComments(node);
      const [firstAfterComment] = sourceCode.getCommentsAfter(node);
      const from = firstBeforeComment || node;
      const to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment) ? firstAfterComment : node;
      return [from.range[0], to.range[1]];
    }

    function checkNodes(nodes: GraphQLESTreeNode<ASTNode>[]) {
      // Starts from 1, ignore nodes.length <= 1
      for (let i = 1; i < nodes.length; i += 1) {
        const currNode = nodes[i];
        const currName = 'name' in currNode && currNode.name?.value;
        if (!currName) {
          // we don't move unnamed current nodes
          continue;
        }

        const prevNode = nodes[i - 1];
        const prevName = 'name' in prevNode && prevNode.name?.value;
        if (prevName) {
          // Compare with lexicographic order
          const compareResult = prevName.localeCompare(currName);
          const shouldSort = compareResult === 1;
          if (!shouldSort) {
            const isSameName = compareResult === 0;
            if (!isSameName || !prevNode.kind.endsWith('Extension') || currNode.kind.endsWith('Extension')) {
              continue;
            }
          }
        }

        context.report({
          node: currNode.name,
          messageId: RULE_ID,
          data: {
            currName,
            prevName: prevName ? `\`${prevName}\`` : lowerCase(prevNode.kind),
          },
          *fix(fixer) {
            const prevRange = getRangeWithComments(prevNode);
            const currRange = getRangeWithComments(currNode);
            yield fixer.replaceTextRange(prevRange, sourceCode.getText({ range: currRange } as any));
            yield fixer.replaceTextRange(currRange, sourceCode.getText({ range: prevRange } as any));
          },
        });
      }
    }

    const opts = context.options[0];
    const fields = new Set(opts.fields ?? []);
    const listeners: GraphQLESLintRuleListener = {};

    const kinds = [
      fields.has(Kind.OBJECT_TYPE_DEFINITION) && [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION],
      fields.has(Kind.INTERFACE_TYPE_DEFINITION) && [Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION],
      fields.has(Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
        Kind.INPUT_OBJECT_TYPE_DEFINITION,
        Kind.INPUT_OBJECT_TYPE_EXTENSION,
      ],
    ]
      .filter(Boolean)
      .flat();

    const fieldsSelector = kinds.join(',');

    const hasEnumValues = opts.values?.[0] === Kind.ENUM_TYPE_DEFINITION;
    const selectionsSelector = opts.selections?.join(',');
    const hasVariables = opts.variables?.[0] === Kind.OPERATION_DEFINITION;
    const argumentsSelector = opts.arguments?.join(',');

    if (fieldsSelector) {
      listeners[fieldsSelector] = (
        node: GraphQLESTreeNode<
          | ObjectTypeDefinitionNode
          | ObjectTypeExtensionNode
          | InterfaceTypeDefinitionNode
          | InterfaceTypeExtensionNode
          | InputObjectTypeDefinitionNode
          | InputObjectTypeExtensionNode
        >
      ) => {
        checkNodes(node.fields);
      };
    }

    if (hasEnumValues) {
      const enumValuesSelector = [Kind.ENUM_TYPE_DEFINITION, Kind.ENUM_TYPE_EXTENSION].join(',');
      listeners[enumValuesSelector] = (node: GraphQLESTreeNode<EnumTypeDefinitionNode | EnumTypeExtensionNode>) => {
        checkNodes(node.values);
      };
    }

    if (selectionsSelector) {
      listeners[`:matches(${selectionsSelector}) SelectionSet`] = (node: GraphQLESTreeNode<SelectionSetNode>) => {
        checkNodes(
          node.selections.map(selection =>
            // sort by alias is field is renamed
            'alias' in selection && selection.alias ? ({ name: selection.alias } as any) : selection
          )
        );
      };
    }

    if (hasVariables) {
      listeners.OperationDefinition = (node: GraphQLESTreeNode<OperationDefinitionNode>) => {
        checkNodes(node.variableDefinitions.map(varDef => varDef.variable));
      };
    }

    if (argumentsSelector) {
      listeners[argumentsSelector] = (
        node: GraphQLESTreeNode<FieldDefinitionNode | FieldNode | DirectiveDefinitionNode | DirectiveNode>
      ) => {
        checkNodes(node.arguments);
      };
    }

    if (opts.definitions) {
      listeners.Document = node => {
        checkNodes(node.definitions);
      };
    }

    return listeners;
  },
}
Example #11
Source File: relay-arguments.ts    From graphql-eslint with MIT License 4 votes vote down vote up
rule: GraphQLESLintRule<[RelayArgumentsConfig], true> = {
  meta: {
    type: 'problem',
    docs: {
      category: 'Schema',
      description: [
        'Set of rules to follow Relay specification for Arguments.',
        '',
        '- A field that returns a Connection type must include forward pagination arguments (`first` and `after`), backward pagination arguments (`last` and `before`), or both',
        '',
        'Forward pagination arguments',
        '',
        '- `first` takes a non-negative integer',
        '- `after` takes the Cursor type',
        '',
        'Backward pagination arguments',
        '',
        '- `last` takes a non-negative integer',
        '- `before` takes the Cursor type',
      ].join('\n'),
      url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
      examples: [
        {
          title: 'Incorrect',
          code: /* GraphQL */ `
            type User {
              posts: PostConnection
            }
          `,
        },
        {
          title: 'Correct',
          code: /* GraphQL */ `
            type User {
              posts(after: String, first: Int, before: String, last: Int): PostConnection
            }
          `,
        },
      ],
      isDisabledForAllConfig: true,
    },
    messages: {
      [MISSING_ARGUMENTS]:
        'A field that returns a Connection type must include forward pagination arguments (`first` and `after`), backward pagination arguments (`last` and `before`), or both.',
    },
    schema: {
      type: 'array',
      maxItems: 1,
      items: {
        type: 'object',
        additionalProperties: false,
        minProperties: 1,
        properties: {
          includeBoth: {
            type: 'boolean',
            default: true,
            description: 'Enforce including both forward and backward pagination arguments',
          },
        },
      },
    },
  },
  create(context) {
    const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
    const { includeBoth = true } = context.options[0] || {};

    return {
      'FieldDefinition > .gqlType Name[value=/Connection$/]'(node: GraphQLESTreeNode<NameNode>) {
        let fieldNode = node.parent;
        while (fieldNode.kind !== Kind.FIELD_DEFINITION) {
          fieldNode = fieldNode.parent as GraphQLESTreeNode<FieldDefinitionNode>;
        }
        const args = Object.fromEntries(fieldNode.arguments.map(argument => [argument.name.value, argument]));
        const hasForwardPagination = Boolean(args.first && args.after);
        const hasBackwardPagination = Boolean(args.last && args.before);

        if (!hasForwardPagination && !hasBackwardPagination) {
          context.report({
            node: fieldNode.name,
            messageId: MISSING_ARGUMENTS,
          });
          return;
        }

        function checkField(typeName: 'String' | 'Int', argumentName: 'first' | 'last' | 'after' | 'before'): void {
          const argument = args[argumentName];
          const hasArgument = Boolean(argument);
          let type = argument as any;
          if (hasArgument && type.gqlType.kind === Kind.NON_NULL_TYPE) {
            type = type.gqlType;
          }
          const isAllowedNonNullType =
            hasArgument &&
            type.gqlType.kind === Kind.NAMED_TYPE &&
            (type.gqlType.name.value === typeName ||
              (typeName === 'String' && isScalarType(schema.getType(type.gqlType.name.value))));

          if (!isAllowedNonNullType) {
            const returnType = typeName === 'String' ? 'String or Scalar' : typeName;
            context.report({
              node: (argument || fieldNode).name,
              message: hasArgument
                ? `Argument \`${argumentName}\` must return ${returnType}.`
                : `Field \`${fieldNode.name.value}\` must contain an argument \`${argumentName}\`, that return ${returnType}.`,
            });
          }
        }

        if (includeBoth || args.first || args.after) {
          checkField('Int', 'first');
          checkField('String', 'after');
        }
        if (includeBoth || args.last || args.before) {
          checkField('Int', 'last');
          checkField('String', 'before');
        }
      },
    };
  },
}
Example #12
Source File: generate-query.ts    From graphql-query-generator with MIT License 4 votes vote down vote up
function getRandomFields(
  fields: ReadonlyArray<FieldDefinitionNode>,
  config: InternalConfiguration,
  schema: GraphQLSchema,
  depth: number
): ReadonlyArray<FieldDefinitionNode> {
  const results = []

  // Create lists of nested and flat fields to pick from
  let nested
  let flat
  if (config.considerInterfaces && config.considerUnions) {
    nested = fields.filter((field) => {
      return (
        isObjectField(field, schema) ||
        isInterfaceField(field, schema) ||
        isUnionField(field, schema)
      )
    })

    flat = fields.filter((field) => {
      return !(
        isObjectField(field, schema) ||
        isInterfaceField(field, schema) ||
        isUnionField(field, schema)
      )
    })
  } else if (config.considerInterfaces! && config.considerUnions) {
    fields = fields.filter((field) => {
      return !isInterfaceField(field, schema)
    })

    nested = fields.filter((field) => {
      return isObjectField(field, schema) || isUnionField(field, schema)
    })

    flat = fields.filter((field) => {
      return !(isObjectField(field, schema) || isUnionField(field, schema))
    })
  } else if (config.considerInterfaces && config.considerUnions!) {
    fields = fields.filter((field) => {
      return !isUnionField(field, schema)
    })

    nested = fields.filter((field) => {
      return isObjectField(field, schema) || isInterfaceField(field, schema)
    })

    flat = fields.filter((field) => {
      return !(isObjectField(field, schema) || isInterfaceField(field, schema))
    })
  } else {
    fields = fields.filter((field) => {
      return !(isInterfaceField(field, schema) || isUnionField(field, schema))
    })

    nested = fields.filter((field) => {
      return isObjectField(field, schema)
    })

    flat = fields.filter((field) => {
      return !isObjectField(field, schema)
    })
  }

  // Filter out fields that only have nested subfields
  if (depth + 2 === config.maxDepth) {
    nested = nested.filter((field) => fieldHasLeafs(field, schema))
  }
  const nextIsLeaf = depth + 1 === config.maxDepth

  const pickNested =
    typeof config.depthProbability === 'number'
      ? random(config) <= config.depthProbability
      : random(config) <= config.depthProbability(depth)

  // If we decide to pick nested, choose one nested field (if one exists)...
  if (
    (pickNested && nested.length > 0 && !nextIsLeaf) ||
    (depth === 0 && config.pickNestedQueryField)
  ) {
    let nestedIndex = Math.floor(random(config) * nested.length)
    results.push(nested[nestedIndex])
    nested.splice(nestedIndex, 1)

    // ...and possibly choose more
    nested.forEach((field) => {
      const pickNested =
        typeof config.breadthProbability === 'number'
          ? random(config) <= config.breadthProbability
          : random(config) <= config.breadthProbability(depth)
      if (pickNested) {
        results.push(field)
      }
    })
  }

  // Pick flat fields based on the breadth probability
  flat.forEach((field) => {
    const pickFlat =
      typeof config.breadthProbability === 'number'
        ? random(config) <= config.breadthProbability
        : random(config) <= config.breadthProbability(depth)
    if (pickFlat) {
      results.push(field)
    }
  })

  // Ensure to pick at least one field
  if (results.length === 0) {
    // If the next level is not the last, we can choose ANY field
    if (!nextIsLeaf) {
      const forcedIndex = Math.floor(random(config) * fields.length)
      results.push(fields[forcedIndex])
      // ...otherwise, we HAVE TO choose a flat field:
    } else if (flat.length > 0) {
      const forcedFlatIndex = Math.floor(random(config) * flat.length)
      results.push(flat[forcedFlatIndex])
    } else {
      // default to selecting __typename meta field:
      results.push(getTypeNameMetaFieldDef())
    }
  }

  return results
}
Example #13
Source File: generate-query.ts    From graphql-query-generator with MIT License 4 votes vote down vote up
function getArgsAndVars(
  field: FieldDefinitionNode,
  nodeName: string,
  config: InternalConfiguration,
  schema: GraphQLSchema,
  providedValues: { [varName: string]: any }
): {
  args: ArgumentNode[]
  variableDefinitionsMap: { [varName: string]: VariableDefinitionNode }
  variableValues: { [varName: string]: any }
} {
  const fieldName = field.name.value
  const allArgs = field.arguments
  const args: ArgumentNode[] = []

  const variableDefinitionsMap: {
    [varName: string]: VariableDefinitionNode
  } = {}

  const requiredArguments = allArgs.filter((arg) =>
    considerArgument(arg, config)
  )

  /**
   * Check for slicing arguments defined in a @listSize directive that should
   * be present:
   */
  const missingSlicingArg = getMissingSlicingArg(
    requiredArguments,
    field,
    schema
  )

  /**
   * Check if missingSlicingArg is already in requiredArguments
   *
   * Because slicing arguments may be deeply nested (input object types), there
   * isn't a simple way to check for conflicts
   */
  if (
    missingSlicingArg &&
    !requiredArguments.find((arg) => {
      return arg.name.value === missingSlicingArg.name.value
    })
  ) {
    requiredArguments.push(missingSlicingArg)
  }

  requiredArguments.forEach((arg) => {
    const varName = `${nodeName}__${fieldName}__${arg.name.value}`
    args.push(getVariable(arg.name.value, varName))
    variableDefinitionsMap[varName] = getVariableDefinition(varName, arg.type)
  })

  const variableValues: { [varName: string]: any } = {}

  // First, check for providers based on type__field query
  // (Note: such a provider must return a value which is an object)
  const { providerFound, value } = getProviderValue(
    `${nodeName}__${fieldName}`,
    config,
    providedValues
  )
  if (providerFound && typeof value === 'object') {
    Object.entries(value).forEach(([argName, value]) => {
      const varName = `${nodeName}__${fieldName}__${argName}`
      // Only consider required arguments (provider can provide more than necessary)
      if (Object.keys(variableDefinitionsMap).includes(varName)) {
        variableValues[varName] = value
      }
    })
  }

  // Second, check for providers based on type__field__argument query
  // (Note: they overwrite possibly already provided values)
  requiredArguments.forEach((arg) => {
    const varName = `${nodeName}__${fieldName}__${arg.name.value}`
    const argType = schema.getType(getTypeName(arg.type))
    const { providerFound, value } = getProviderValue(
      varName,
      config,
      { ...variableValues, ...providedValues }, // pass already used variable values
      argType
    )
    if (providerFound) {
      variableValues[varName] = value
    }
  })

  // Third, populate all so-far neglected require variables with defaults or null
  requiredArguments.forEach((arg) => {
    const varName = `${nodeName}__${fieldName}__${arg.name.value}`
    const argType = schema.getType(getTypeName(arg.type))
    if (typeof variableValues[varName] === 'undefined') {
      if (isEnumType(argType)) {
        // Enum values can be randomly selected
        variableValues[varName] = getRandomEnum(argType)
      } else if (config.providePlaceholders) {
        variableValues[varName] = getDefaultArgValue(schema, config, arg.type)

        // Add default values for unfulfilled listSize directive if applicable
        const listSizeDirective = getListSizeDirective(field)
        if (listSizeDirective) {
          const value = getListSizeDirectiveDefaultValue(
            listSizeDirective,
            arg.type,
            config,
            schema
          )

          if (value !== undefined) {
            if (typeof value !== 'object') {
              variableValues[varName] = value
            } else {
              variableValues[varName] = merge.recursive(
                value,
                variableValues[varName]
              )
            }
          }
        }
      } else if (arg.type.kind === 'NonNullType') {
        throw new Error(
          `Missing provider for non-null variable "${varName}" of type "${print(
            arg.type
          )}". ` +
            `Either add a provider (e.g., using a wildcard "*__*" or "*__*__*"), ` +
            `or set providePlaceholders configuration option to true.`
        )
      } else {
        variableValues[varName] = null
      }
    }
  })

  return {
    args,
    variableDefinitionsMap,
    variableValues
  }
}