graphql-js icon indicating copy to clipboard operation
graphql-js copied to clipboard

Custom rule breaks NoUnusedVariablesRule

Open alanchristensen opened this issue 2 years ago • 2 comments

I created a rule that visits a fragment's selectionSet when it encounters a FRAGMENT_SPREAD. The rule itself worked as desired, however doing so caused NoUnusedVariablesRule to fail if a variable was used in a fragment spread. It appears that my custom rule is changing the visitor in the default rules.

Here's a slimmed down version of my code that demonstrates the problem:

import { buildSchema, parse, validate, NoUnusedVariablesRule, Kind, ValidationContext, ASTVisitor } from 'graphql';

function CustomRule(context: ValidationContext): ASTVisitor {
  return {
    enter: (node) => {
      // If FRAGMENT_SPREAD, then return the selectionSet of the fragment definition
      // This causes visitor to visit the selectionSet instead of ending on the current node
      if (node.kind === Kind.FRAGMENT_SPREAD) {
        return context.getFragment(node.name.value)?.selectionSet;
      }

      return undefined;
    },
    leave: () => {},
  };
}

const schema = buildSchema(/* GraphQL */ `
  type Author {
    name: String
  }
  type Query {
    author: Author
  }
`);

const doc = parse(/* GraphQL */ `
  query AUTHOR($s: Boolean = false) {
    author {
      ...FRAG @skip(if: $s)
    }
  }

  fragment FRAG on Author {
    name
  }
`);

console.debug(validate(schema, doc, [NoUnusedVariablesRule, CustomRule]));

This causes a GraphQLError: Variable "$s" is never used in operation "AUTHOR". error. If you remove the CustomRule then no errors are reported.

alanchristensen avatar Apr 26 '23 22:04 alanchristensen

@alanchristensen Wow, this is unexpected behavior. I will take a look, at what is happening here.

IvanGoncharov avatar May 05 '23 19:05 IvanGoncharov

I wonder whether this actually is unexpected behavior we are mutating our operation inside of a validation-rule. The result of this mutation will be the following

query AUTHOR($s: Boolean = false) {
  author {
    name
  }
}

As we omitted the @skip, our variable $s indeed becomes unused, I am uncertain whether the intention here would be to i.e. copy over the directive to all selections of the root-selection of the fragment. That however would fix the issue.

We don't really take the variables into account during validation so we don't know whether this fragment-spread will in fact be skipped.

JoviDeCroock avatar Jul 21 '24 07:07 JoviDeCroock