graphql#NameNode TypeScript Examples

The following examples show how to use graphql#NameNode. 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: gqlTypes.ts    From ra-data-prisma with MIT License 6 votes vote down vote up
operationDefinition = (
  operation: OperationTypeNode,
  selectionSet: SelectionSetNode,
  name: NameNode,
  variableDefinitions: VariableDefinitionNode[],
): OperationDefinitionNode => ({
  kind: Kind.OPERATION_DEFINITION,
  operation,
  selectionSet,
  name,
  variableDefinitions,
})
Example #2
Source File: generate-query.ts    From graphql-query-generator with MIT License 5 votes vote down vote up
function getName(name: string): NameNode {
  return {
    kind: Kind.NAME,
    value: name
  }
}
Example #3
Source File: gqlTypes.ts    From ra-data-prisma with MIT License 5 votes vote down vote up
argument = (name: NameNode, value: ValueNode): ArgumentNode => ({
  kind: Kind.ARGUMENT,
  name,
  value,
})
Example #4
Source File: gqlTypes.ts    From ra-data-prisma with MIT License 5 votes vote down vote up
namedType = (name: NameNode): NamedTypeNode => ({
  kind: Kind.NAMED_TYPE,
  name,
})
Example #5
Source File: gqlTypes.ts    From ra-data-prisma with MIT License 5 votes vote down vote up
name = (value: string): NameNode => ({
  kind: Kind.NAME,
  value,
})
Example #6
Source File: gqlTypes.ts    From ra-data-prisma with MIT License 5 votes vote down vote up
variable = (name: NameNode): VariableNode => ({
  kind: Kind.VARIABLE,
  name,
})
Example #7
Source File: gqlTypes.ts    From ra-data-prisma with MIT License 5 votes vote down vote up
field = (
  name: NameNode,
  optionalValues: Partial<FieldNode> = {},
): FieldNode => ({
  kind: Kind.FIELD,
  name,
  ...optionalValues,
})
Example #8
Source File: swift-declaration-block.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
withName(name: string | NameNode): SwiftDeclarationBlock {
    this._name = typeof name === 'object' ? (name as NameNode).value : name;

    return this;
  }
Example #9
Source File: java-declaration-block.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
withName(name: string | NameNode): JavaDeclarationBlock {
    this._name = typeof name === 'object' ? (name as NameNode).value : name;

    return this;
  }
Example #10
Source File: dart-declaration-block.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
withName(name: string | NameNode): DartDeclarationBlock {
    this._name = typeof name === 'object' ? (name as NameNode).value : name;
    return this;
  }
Example #11
Source File: require-field-of-type-query-in-mutation-result.ts    From graphql-eslint with MIT License 5 votes vote down vote up
rule: GraphQLESLintRule = {
  meta: {
    type: 'suggestion',
    docs: {
      category: 'Schema',
      description:
        'Allow the client in one round-trip not only to call mutation but also to get a wagon of data to update their application.\n> Currently, no errors are reported for result type `union`, `interface` and `scalar`.',
      url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
      requiresSchema: true,
      examples: [
        {
          title: 'Incorrect',
          code: /* GraphQL */ `
            type User { ... }

            type Mutation {
              createUser: User!
            }
          `,
        },
        {
          title: 'Correct',
          code: /* GraphQL */ `
            type User { ... }

            type Query { ... }

            type CreateUserPayload {
              user: User!
              query: Query!
            }

            type Mutation {
              createUser: CreateUserPayload!
            }
          `,
        },
      ],
    },
    schema: [],
  },
  create(context) {
    const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
    const mutationType = schema.getMutationType();
    const queryType = schema.getQueryType();

    if (!mutationType || !queryType) {
      return {};
    }
    const selector = `:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=${mutationType.name}] > FieldDefinition > .gqlType Name`;

    return {
      [selector](node: GraphQLESTreeNode<NameNode>) {
        const typeName = node.value;
        const graphQLType = schema.getType(typeName);

        if (isObjectType(graphQLType)) {
          const { fields } = graphQLType.astNode;
          const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
          if (!hasQueryType) {
            context.report({
              node,
              message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}"`,
            });
          }
        }
      },
    };
  },
}
Example #12
Source File: no-unreachable-types.ts    From graphql-eslint with MIT License 5 votes vote down vote up
rule: GraphQLESLintRule = {
  meta: {
    messages: {
      [RULE_ID]: '{{ type }} `{{ typeName }}` is unreachable.',
    },
    docs: {
      description: 'Requires all types to be reachable at some level by root level fields.',
      category: 'Schema',
      url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
      requiresSchema: true,
      examples: [
        {
          title: 'Incorrect',
          code: /* GraphQL */ `
            type User {
              id: ID!
              name: String
            }

            type Query {
              me: String
            }
          `,
        },
        {
          title: 'Correct',
          code: /* GraphQL */ `
            type User {
              id: ID!
              name: String
            }

            type Query {
              me: User
            }
          `,
        },
      ],
      recommended: true,
    },
    type: 'suggestion',
    schema: [],
    hasSuggestions: true,
  },
  create(context) {
    const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
    const reachableTypes = getReachableTypes(schema);

    return {
      [`:matches(${KINDS}) > .name`](node: GraphQLESTreeNode<NameNode>) {
        const typeName = node.value;

        if (!reachableTypes.has(typeName)) {
          const type = lowerCase(node.parent.kind.replace(/(Extension|Definition)$/, ''));
          context.report({
            node,
            messageId: RULE_ID,
            data: {
              type: type[0].toUpperCase() + type.slice(1),
              typeName,
            },
            suggest: [
              {
                desc: `Remove \`${typeName}\``,
                fix: fixer => fixer.remove(node.parent as any),
              },
            ],
          });
        }
      },
    };
  },
}
Example #13
Source File: no-scalar-result-type-on-mutation.ts    From graphql-eslint with MIT License 5 votes vote down vote up
rule: GraphQLESLintRule = {
  meta: {
    type: 'suggestion',
    hasSuggestions: true,
    docs: {
      category: 'Schema',
      description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
      url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
      requiresSchema: true,
      examples: [
        {
          title: 'Incorrect',
          code: /* GraphQL */ `
            type Mutation {
              createUser: Boolean
            }
          `,
        },
        {
          title: 'Correct',
          code: /* GraphQL */ `
            type Mutation {
              createUser: User!
            }
          `,
        },
      ],
    },
    schema: [],
  },
  create(context) {
    const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
    const mutationType = schema.getMutationType();
    if (!mutationType) {
      return {};
    }
    const selector = [
      `:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=${mutationType.name}]`,
      '> FieldDefinition > .gqlType Name',
    ].join(' ');

    return {
      [selector](node: GraphQLESTreeNode<NameNode>) {
        const typeName = node.value;
        const graphQLType = schema.getType(typeName);
        if (isScalarType(graphQLType)) {
          context.report({
            node,
            message: `Unexpected scalar result type \`${typeName}\`.`,
            suggest: [
              {
                desc: `Remove \`${typeName}\``,
                fix: fixer => fixer.remove(node as any),
              },
            ],
          });
        }
      },
    };
  },
}
Example #14
Source File: input-name.ts    From graphql-eslint with MIT License 4 votes vote down vote up
rule: GraphQLESLintRule<[InputNameRuleConfig]> = {
  meta: {
    type: 'suggestion',
    hasSuggestions: true,
    docs: {
      description:
        'Require mutation argument to be always called "input" and input type to be called Mutation name + "Input".\nUsing the same name for all input parameters will make your schemas easier to consume and more predictable. Using the same name as mutation for InputType will make it easier to find mutations that InputType belongs to.',
      category: 'Schema',
      url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/input-name.md',
      examples: [
        {
          title: 'Incorrect',
          usage: [{ checkInputType: true }],
          code: /* GraphQL */ `
            type Mutation {
              SetMessage(message: InputMessage): String
            }
          `,
        },
        {
          title: 'Correct (with checkInputType)',
          usage: [{ checkInputType: true }],
          code: /* GraphQL */ `
            type Mutation {
              SetMessage(input: SetMessageInput): String
            }
          `,
        },
        {
          title: 'Correct (without checkInputType)',
          usage: [{ checkInputType: false }],
          code: /* GraphQL */ `
            type Mutation {
              SetMessage(input: AnyInputTypeName): String
            }
          `,
        },
      ],
    },
    schema: [
      {
        type: 'object',
        additionalProperties: false,
        properties: {
          checkInputType: {
            type: 'boolean',
            default: false,
            description: 'Check that the input type name follows the convention <mutationName>Input',
          },
          caseSensitiveInputType: {
            type: 'boolean',
            default: true,
            description: 'Allow for case discrepancies in the input type name',
          },
          checkQueries: {
            type: 'boolean',
            default: false,
            description: 'Apply the rule to Queries',
          },
          checkMutations: {
            type: 'boolean',
            default: true,
            description: 'Apply the rule to Mutations',
          },
        },
      },
    ],
  },
  create(context) {
    const options: InputNameRuleConfig = {
      checkInputType: false,
      caseSensitiveInputType: true,
      checkQueries: false,
      checkMutations: true,
      ...context.options[0],
    };

    const shouldCheckType = node =>
      (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));

    const listeners: GraphQLESLintRuleListener = {
      'FieldDefinition > InputValueDefinition[name.value!=input] > Name'(node: GraphQLESTreeNode<NameNode>) {
        if (shouldCheckType((node as any).parent.parent.parent)) {
          const inputName = node.value;
          context.report({
            node,
            message: `Input \`${inputName}\` should be called \`input\`.`,
            suggest: [
              {
                desc: 'Rename to `input`',
                fix: fixer => fixer.replaceText(node as any, 'input'),
              },
            ],
          });
        }
      },
    };

    if (options.checkInputType) {
      listeners['FieldDefinition > InputValueDefinition NamedType'] = (node: GraphQLESTreeNode<NamedTypeNode>) => {
        const findInputType = item => {
          let currentNode = item;
          while (currentNode.type !== Kind.INPUT_VALUE_DEFINITION) {
            currentNode = currentNode.parent;
          }
          return currentNode;
        };

        const inputValueNode = findInputType(node);
        if (shouldCheckType(inputValueNode.parent.parent)) {
          const mutationName = `${inputValueNode.parent.name.value}Input`;
          const name = node.name.value;
          if (
            (options.caseSensitiveInputType && node.name.value !== mutationName) ||
            name.toLowerCase() !== mutationName.toLowerCase()
          ) {
            context.report({
              node: node.name,
              message: `Input type \`${name}\` name should be \`${mutationName}\`.`,
              suggest: [
                {
                  desc: `Rename to \`${mutationName}\``,
                  fix: fixer => fixer.replaceText(node as any, mutationName),
                },
              ],
            });
          }
        }
      };
    }
    return listeners;
  },
}
Example #15
Source File: relay-edge-types.ts    From graphql-eslint with MIT License 4 votes vote down vote up
rule: GraphQLESLintRule<[EdgeTypesConfig], true> = {
  meta: {
    type: 'problem',
    docs: {
      category: 'Schema',
      description: [
        'Set of rules to follow Relay specification for Edge types.',
        '',
        "- A type that is returned in list form by a connection type's `edges` field is considered by this spec to be an Edge type",
        '- Edge type must be an Object type',
        '- Edge type must contain a field `node` that return either Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types. Notably, this field cannot return a list',
        '- Edge type must contain a field `cursor` that return either String, Scalar, or a non-null wrapper around one of those types',
        '- Edge type name must end in "Edge" _(optional)_',
        "- Edge type's field `node` must implement `Node` interface _(optional)_",
        '- A list type should only wrap an edge type _(optional)_',
      ].join('\n'),
      url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
      isDisabledForAllConfig: true,
      requiresSchema: true,
      examples: [
        {
          title: 'Correct',
          code: /* GraphQL */ `
            type UserConnection {
              edges: [UserEdge]
              pageInfo: PageInfo!
            }
          `,
        },
      ],
    },
    messages: {
      [MESSAGE_MUST_BE_OBJECT_TYPE]: 'Edge type must be an Object type.',
      [MESSAGE_MISSING_EDGE_SUFFIX]: 'Edge type must have "Edge" suffix.',
      [MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE]: 'A list type should only wrap an edge type.',
      [MESSAGE_SHOULD_IMPLEMENTS_NODE]: "Edge type's field `node` must implement `Node` interface.",
    },
    schema: {
      type: 'array',
      maxItems: 1,
      items: {
        type: 'object',
        additionalProperties: false,
        minProperties: 1,
        properties: {
          withEdgeSuffix: {
            type: 'boolean',
            default: true,
            description: 'Edge type name must end in "Edge".',
          },
          shouldImplementNode: {
            type: 'boolean',
            default: true,
            description: "Edge type's field `node` must implement `Node` interface.",
          },
          listTypeCanWrapOnlyEdgeType: {
            type: 'boolean',
            default: true,
            description: 'A list type should only wrap an edge type.',
          },
        },
      },
    },
  },
  create(context) {
    const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
    const edgeTypes = getEdgeTypes(schema);
    const options: EdgeTypesConfig = {
      withEdgeSuffix: true,
      shouldImplementNode: true,
      listTypeCanWrapOnlyEdgeType: true,
      ...context.options[0],
    };

    const isNamedOrNonNullNamed = (node: GraphQLESTreeNode<TypeNode>): boolean =>
      node.kind === Kind.NAMED_TYPE || (node.kind === Kind.NON_NULL_TYPE && node.gqlType.kind === Kind.NAMED_TYPE);

    const checkNodeField = (node: GraphQLESTreeNode<ObjectTypeDefinitionNode>): void => {
      const nodeField = node.fields.find(field => field.name.value === 'node');
      const message =
        'return either a Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types.';
      if (!nodeField) {
        context.report({
          node: node.name,
          message: `Edge type must contain a field \`node\` that ${message}`,
        });
      } else if (!isNamedOrNonNullNamed(nodeField.gqlType)) {
        context.report({ node: nodeField.name, message: `Field \`node\` must ${message}` });
      } else if (options.shouldImplementNode) {
        const nodeReturnTypeName = getTypeName(nodeField.gqlType.rawNode());
        const type = schema.getType(nodeReturnTypeName);
        if (!isObjectType(type)) {
          return;
        }
        const implementsNode = type.astNode.interfaces.some(n => n.name.value === 'Node');
        if (!implementsNode) {
          context.report({ node: node.name, messageId: MESSAGE_SHOULD_IMPLEMENTS_NODE });
        }
      }
    };

    const checkCursorField = (node: GraphQLESTreeNode<ObjectTypeDefinitionNode>): void => {
      const cursorField = node.fields.find(field => field.name.value === 'cursor');
      const message = 'return either a String, Scalar, or a non-null wrapper wrapper around one of those types.';
      if (!cursorField) {
        context.report({
          node: node.name,
          message: `Edge type must contain a field \`cursor\` that ${message}`,
        });
        return;
      }
      const typeName = getTypeName(cursorField.rawNode());
      if (
        !isNamedOrNonNullNamed(cursorField.gqlType) ||
        (typeName !== 'String' && !isScalarType(schema.getType(typeName)))
      ) {
        context.report({ node: cursorField.name, message: `Field \`cursor\` must ${message}` });
      }
    };

    const listeners: GraphQLESLintRuleListener<true> = {
      ':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=edges] > .gqlType Name'(
        node: GraphQLESTreeNode<NameNode>
      ) {
        const type = schema.getType(node.value);
        if (!isObjectType(type)) {
          context.report({ node, messageId: MESSAGE_MUST_BE_OBJECT_TYPE });
        }
      },
      ':matches(ObjectTypeDefinition, ObjectTypeExtension)'(node: GraphQLESTreeNode<ObjectTypeDefinitionNode>) {
        const typeName = node.name.value;
        if (edgeTypes.has(typeName)) {
          checkNodeField(node);
          checkCursorField(node);
          if (options.withEdgeSuffix && !typeName.endsWith('Edge')) {
            context.report({ node: node.name, messageId: MESSAGE_MISSING_EDGE_SUFFIX });
          }
        }
      },
    };

    if (options.listTypeCanWrapOnlyEdgeType) {
      listeners['FieldDefinition > .gqlType'] = (node: GraphQLESTreeNode<TypeNode>) => {
        if (
          node.kind === Kind.LIST_TYPE ||
          (node.kind === Kind.NON_NULL_TYPE && node.gqlType.kind === Kind.LIST_TYPE)
        ) {
          const typeName = getTypeName(node.rawNode());
          if (!edgeTypes.has(typeName)) {
            context.report({ node, messageId: MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE });
          }
        }
      };
    }

    return listeners;
  },
}
Example #16
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 #17
Source File: no-root-type.ts    From graphql-eslint with MIT License 4 votes vote down vote up
rule: GraphQLESLintRule<[NoRootTypeConfig]> = {
  meta: {
    type: 'suggestion',
    hasSuggestions: true,
    docs: {
      category: 'Schema',
      description: 'Disallow using root types `mutation` and/or `subscription`.',
      url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/no-root-type.md',
      requiresSchema: true,
      isDisabledForAllConfig: true,
      examples: [
        {
          title: 'Incorrect',
          usage: [{ disallow: ['mutation', 'subscription'] }],
          code: /* GraphQL */ `
            type Mutation {
              createUser(input: CreateUserInput!): User!
            }
          `,
        },
        {
          title: 'Correct',
          usage: [{ disallow: ['mutation', 'subscription'] }],
          code: /* GraphQL */ `
            type Query {
              users: [User!]!
            }
          `,
        },
      ],
    },
    schema: {
      type: 'array',
      minItems: 1,
      maxItems: 1,
      items: {
        type: 'object',
        additionalProperties: false,
        required: ['disallow'],
        properties: {
          disallow: {
            ...ARRAY_DEFAULT_OPTIONS,
            items: {
              enum: ROOT_TYPES,
            },
          },
        },
      },
    },
  },
  create(context) {
    const schema = requireGraphQLSchemaFromContext('no-root-type', context);
    const disallow = new Set(context.options[0].disallow);

    const rootTypeNames = [
      disallow.has('mutation') && schema.getMutationType(),
      disallow.has('subscription') && schema.getSubscriptionType(),
    ]
      .filter(Boolean)
      .map(type => type.name)
      .join('|');

    if (!rootTypeNames) {
      return {};
    }

    const selector = `:matches(ObjectTypeDefinition, ObjectTypeExtension) > .name[value=/^(${rootTypeNames})$/]`;

    return {
      [selector](node: GraphQLESTreeNode<NameNode>) {
        const typeName = node.value;
        context.report({
          node,
          message: `Root type \`${typeName}\` is forbidden.`,
          suggest: [
            {
              desc: `Remove \`${typeName}\` type`,
              fix: fixer => fixer.remove(node.parent as any),
            },
          ],
        });
      },
    };
  },
}
Example #18
Source File: no-duplicate-fields.ts    From graphql-eslint with MIT License 4 votes vote down vote up
rule: GraphQLESLintRule = {
  meta: {
    type: 'suggestion',
    hasSuggestions: true,
    docs: {
      description:
        'Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.',
      category: 'Operations',
      url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
      recommended: true,
      examples: [
        {
          title: 'Incorrect',
          code: /* GraphQL */ `
            query {
              user {
                name
                email
                name # duplicate field
              }
            }
          `,
        },
        {
          title: 'Incorrect',
          code: /* GraphQL */ `
            query {
              users(
                first: 100
                skip: 50
                after: "cji629tngfgou0b73kt7vi5jo"
                first: 100 # duplicate argument
              ) {
                id
              }
            }
          `,
        },
        {
          title: 'Incorrect',
          code: /* GraphQL */ `
            query (
              $first: Int!
              $first: Int! # duplicate variable
            ) {
              users(first: $first, skip: 50) {
                id
              }
            }
          `,
        },
      ],
    },
    messages: {
      [RULE_ID]: '{{ type }} `{{ fieldName }}` defined multiple times.',
    },
    schema: [],
  },
  create(context) {
    function checkNode(usedFields: Set<string>, node: GraphQLESTreeNode<NameNode>): void {
      const fieldName = node.value;
      if (usedFields.has(fieldName)) {
        const { parent } = node;
        context.report({
          node,
          messageId: RULE_ID,
          data: {
            type: parent.type,
            fieldName,
          },
          suggest: [
            {
              desc: `Remove \`${fieldName}\` ${parent.type.toLowerCase()}`,
              fix(fixer) {
                return fixer.remove((parent.type === Kind.VARIABLE ? parent.parent : parent) as any);
              },
            },
          ],
        });
      } else {
        usedFields.add(fieldName);
      }
    }

    return {
      OperationDefinition(node) {
        const set = new Set<string>();
        for (const varDef of node.variableDefinitions) {
          checkNode(set, varDef.variable.name);
        }
      },
      Field(node) {
        const set = new Set<string>();
        for (const arg of node.arguments) {
          checkNode(set, arg.name);
        }
      },
      SelectionSet(node) {
        const set = new Set<string>();
        for (const selection of node.selections) {
          if (selection.kind === Kind.FIELD) {
            checkNode(set, selection.alias || selection.name);
          }
        }
      },
    };
  },
}
Example #19
Source File: naming-convention.ts    From graphql-eslint with MIT License 4 votes vote down vote up
rule: GraphQLESLintRule<[NamingConventionRuleConfig]> = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'Require names to follow specified conventions.',
      category: ['Schema', 'Operations'],
      recommended: true,
      url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/naming-convention.md',
      examples: [
        {
          title: 'Incorrect',
          usage: [{ types: 'PascalCase', FieldDefinition: 'camelCase' }],
          code: /* GraphQL */ `
            type user {
              first_name: String!
            }
          `,
        },
        {
          title: 'Incorrect',
          usage: [{ FragmentDefinition: { style: 'PascalCase', forbiddenSuffixes: ['Fragment'] } }],
          code: /* GraphQL */ `
            fragment UserFragment on User {
              # ...
            }
          `,
        },
        {
          title: 'Incorrect',
          usage: [{ 'FieldDefinition[parent.name.value=Query]': { forbiddenPrefixes: ['get'] } }],
          code: /* GraphQL */ `
            type Query {
              getUsers: [User!]!
            }
          `,
        },
        {
          title: 'Correct',
          usage: [{ types: 'PascalCase', FieldDefinition: 'camelCase' }],
          code: /* GraphQL */ `
            type User {
              firstName: String
            }
          `,
        },
        {
          title: 'Correct',
          usage: [{ FragmentDefinition: { style: 'PascalCase', forbiddenSuffixes: ['Fragment'] } }],
          code: /* GraphQL */ `
            fragment UserFields on User {
              # ...
            }
          `,
        },
        {
          title: 'Correct',
          usage: [{ 'FieldDefinition[parent.name.value=Query]': { forbiddenPrefixes: ['get'] } }],
          code: /* GraphQL */ `
            type Query {
              users: [User!]!
            }
          `,
        },
        {
          title: 'Correct',
          usage: [{ FieldDefinition: { style: 'camelCase', ignorePattern: '^(EAN13|UPC|UK)' } }],
          code: /* GraphQL */ `
            type Product {
              EAN13: String
              UPC: String
              UKFlag: String
            }
          `,
        },
      ],
      configOptions: {
        schema: [
          {
            types: 'PascalCase',
            FieldDefinition: 'camelCase',
            InputValueDefinition: 'camelCase',
            Argument: 'camelCase',
            DirectiveDefinition: 'camelCase',
            EnumValueDefinition: 'UPPER_CASE',
            'FieldDefinition[parent.name.value=Query]': {
              forbiddenPrefixes: ['query', 'get'],
              forbiddenSuffixes: ['Query'],
            },
            'FieldDefinition[parent.name.value=Mutation]': {
              forbiddenPrefixes: ['mutation'],
              forbiddenSuffixes: ['Mutation'],
            },
            'FieldDefinition[parent.name.value=Subscription]': {
              forbiddenPrefixes: ['subscription'],
              forbiddenSuffixes: ['Subscription'],
            },
          },
        ],
        operations: [
          {
            VariableDefinition: 'camelCase',
            OperationDefinition: {
              style: 'PascalCase',
              forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
              forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
            },
            FragmentDefinition: {
              style: 'PascalCase',
              forbiddenPrefixes: ['Fragment'],
              forbiddenSuffixes: ['Fragment'],
            },
          },
        ],
      },
    },
    hasSuggestions: true,
    schema: {
      definitions: {
        asString: {
          enum: ALLOWED_STYLES,
          description: `One of: ${ALLOWED_STYLES.map(t => `\`${t}\``).join(', ')}`,
        },
        asObject: {
          type: 'object',
          additionalProperties: false,
          properties: {
            style: { enum: ALLOWED_STYLES },
            prefix: { type: 'string' },
            suffix: { type: 'string' },
            forbiddenPrefixes: ARRAY_DEFAULT_OPTIONS,
            forbiddenSuffixes: ARRAY_DEFAULT_OPTIONS,
            ignorePattern: {
              type: 'string',
              description: 'Option to skip validation of some words, e.g. acronyms',
            },
          },
        },
      },
      type: 'array',
      maxItems: 1,
      items: {
        type: 'object',
        additionalProperties: false,
        properties: {
          types: {
            ...schemaOption,
            description: `Includes:\n\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
          },
          ...Object.fromEntries(
            ALLOWED_KINDS.map(kind => [
              kind,
              {
                ...schemaOption,
                description: `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`,
              },
            ])
          ),
          allowLeadingUnderscore: {
            type: 'boolean',
            default: false,
          },
          allowTrailingUnderscore: {
            type: 'boolean',
            default: false,
          },
        },
        patternProperties: {
          [`^(${ALLOWED_KINDS.join('|')})(.+)?$`]: schemaOption,
        },
        description: [
          "> It's possible to use a [`selector`](https://eslint.org/docs/developer-guide/selectors) that starts with allowed `ASTNode` names which are described below.",
          '>',
          '> Paste or drop code into the editor in [ASTExplorer](https://astexplorer.net) and inspect the generated AST to compose your selector.',
          '>',
          '> Example: pattern property `FieldDefinition[parent.name.value=Query]` will match only fields for type `Query`.',
        ].join('\n'),
      },
    },
  },
  create(context) {
    const options = context.options[0] || {};
    const { allowLeadingUnderscore, allowTrailingUnderscore, types, ...restOptions } = options;

    function normalisePropertyOption(kind: string): PropertySchema {
      const style: Options = restOptions[kind] || types;
      return typeof style === 'object' ? style : { style };
    }

    function report(node: GraphQLESTreeNode<NameNode>, message: string, suggestedName: string): void {
      context.report({
        node,
        message,
        suggest: [
          {
            desc: `Rename to \`${suggestedName}\``,
            fix: fixer => fixer.replaceText(node as any, suggestedName),
          },
        ],
      });
    }

    const checkNode = (selector: string) => (n: GraphQLESTreeNode<ValueOf<AllowedKindToNode>>) => {
      const { name: node } = n.kind === Kind.VARIABLE_DEFINITION ? n.variable : n;
      if (!node) {
        return;
      }
      const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style, ignorePattern } =
        normalisePropertyOption(selector);
      const nodeType = KindToDisplayName[n.kind] || n.kind;
      const nodeName = node.value;
      const error = getError();
      if (error) {
        const { errorMessage, renameToName } = error;
        const [leadingUnderscores] = nodeName.match(/^_*/);
        const [trailingUnderscores] = nodeName.match(/_*$/);
        const suggestedName = leadingUnderscores + renameToName + trailingUnderscores;
        report(node, `${nodeType} "${nodeName}" should ${errorMessage}`, suggestedName);
      }

      function getError(): {
        errorMessage: string;
        renameToName: string;
      } | void {
        const name = nodeName.replace(/(^_+)|(_+$)/g, '');
        if (ignorePattern && new RegExp(ignorePattern, 'u').test(name)) {
          return;
        }
        if (prefix && !name.startsWith(prefix)) {
          return {
            errorMessage: `have "${prefix}" prefix`,
            renameToName: prefix + name,
          };
        }
        if (suffix && !name.endsWith(suffix)) {
          return {
            errorMessage: `have "${suffix}" suffix`,
            renameToName: name + suffix,
          };
        }
        const forbiddenPrefix = forbiddenPrefixes?.find(prefix => name.startsWith(prefix));
        if (forbiddenPrefix) {
          return {
            errorMessage: `not have "${forbiddenPrefix}" prefix`,
            renameToName: name.replace(new RegExp(`^${forbiddenPrefix}`), ''),
          };
        }
        const forbiddenSuffix = forbiddenSuffixes?.find(suffix => name.endsWith(suffix));
        if (forbiddenSuffix) {
          return {
            errorMessage: `not have "${forbiddenSuffix}" suffix`,
            renameToName: name.replace(new RegExp(`${forbiddenSuffix}$`), ''),
          };
        }
        // Style is optional
        if (!style) {
          return;
        }
        const caseRegex = StyleToRegex[style];
        if (!caseRegex.test(name)) {
          return {
            errorMessage: `be in ${style} format`,
            renameToName: convertCase(style, name),
          };
        }
      }
    };

    const checkUnderscore = (isLeading: boolean) => (node: GraphQLESTreeNode<NameNode>) => {
      const suggestedName = node.value.replace(isLeading ? /^_+/ : /_+$/, '');
      report(node, `${isLeading ? 'Leading' : 'Trailing'} underscores are not allowed`, suggestedName);
    };

    const listeners: GraphQLESLintRuleListener = {};

    if (!allowLeadingUnderscore) {
      listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
        checkUnderscore(true);
    }
    if (!allowTrailingUnderscore) {
      listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
        checkUnderscore(false);
    }

    const selectors = new Set([types && TYPES_KINDS, Object.keys(restOptions)].flat().filter(Boolean));

    for (const selector of selectors) {
      listeners[selector] = checkNode(selector);
    }
    return listeners;
  },
}