module GraphQL::StaticValidation::VariablesAreUsedAndDefined

def create_errors(node_variables)

Then push messages into the validation context
Determine all the error messages,
def create_errors(node_variables)
  # Declared but not used:
  node_variables
    .select { |name, usage| usage.declared? && !usage.used? }
    .each { |var_name, usage|
      declared_by_error_name = usage.declared_by.name || "anonymous #{usage.declared_by.operation_type}"
      add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new(
        "Variable $#{var_name} is declared by #{declared_by_error_name} but not used",
        nodes: usage.declared_by,
        path: usage.path,
        name: var_name,
        error_type: VariablesAreUsedAndDefinedError::VIOLATIONS[:VARIABLE_NOT_USED]
      ))
    }
  # Used but not declared:
  node_variables
    .select { |name, usage| usage.used? && !usage.declared? }
    .each { |var_name, usage|
      used_by_error_name = usage.used_by.name || "anonymous #{usage.used_by.operation_type}"
      add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new(
        "Variable $#{var_name} is used by #{used_by_error_name} but not declared",
        nodes: usage.ast_node,
        path: usage.path,
        name: var_name,
        error_type: VariablesAreUsedAndDefinedError::VIOLATIONS[:VARIABLE_NOT_DEFINED]
      ))
    }
end

def follow_spreads(node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments)

Avoid infinite loops by skipping anything in `visited_fragments`.
Use those fragments to update {VariableUsage}s in `parent_variables`.
Follow spreads in `node`, looking them up from `spreads_for_context` and finding their match in `fragment_definitions`.
def follow_spreads(node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments)
  spreads = spreads_for_context[node] - visited_fragments
  spreads.each do |spread_name|
    def_node = nil
    variables = nil
    # Implement `.find` by hand to avoid Ruby's internal allocations
    fragment_definitions.each do |frag_def_node, vars|
      if frag_def_node.name == spread_name
        def_node = frag_def_node
        variables = vars
        break
      end
    end
    next if !def_node
    visited_fragments << spread_name
    variables.each do |name, child_usage|
      parent_usage = parent_variables[name]
      if child_usage.used?
        parent_usage.ast_node   = child_usage.ast_node
        parent_usage.used_by    = child_usage.used_by
        parent_usage.path       = child_usage.path
      end
    end
    follow_spreads(def_node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments)
  end
end

def initialize(*)

def initialize(*)
  super
  @variable_usages_for_context = Hash.new {|hash, key| hash[key] = Hash.new {|h, k| h[k] = VariableUsage.new } }
  @spreads_for_context = Hash.new {|hash, key| hash[key] = [] }
  @variable_context_stack = []
end

def on_document(node, parent)

def on_document(node, parent)
  super
  fragment_definitions = @variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::FragmentDefinition) }
  operation_definitions = @variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::OperationDefinition) }
  operation_definitions.each do |node, node_variables|
    follow_spreads(node, node_variables, @spreads_for_context, fragment_definitions, [])
    create_errors(node_variables)
  end
end

def on_fragment_definition(node, parent)

def on_fragment_definition(node, parent)
  # initialize the hash of vars for this context:
  @variable_usages_for_context[node]
  @variable_context_stack.push(node)
  super
  @variable_context_stack.pop
end

def on_fragment_spread(node, parent)

- mark the context as containing this spread
- find the context on the stack
For FragmentSpreads:
def on_fragment_spread(node, parent)
  variable_context = @variable_context_stack.last
  @spreads_for_context[variable_context] << node.name
  super
end

def on_operation_definition(node, parent)

def on_operation_definition(node, parent)
  # initialize the hash of vars for this context:
  @variable_usages_for_context[node]
  @variable_context_stack.push(node)
  # mark variables as defined:
  var_hash = @variable_usages_for_context[node]
  node.variables.each { |var|
    var_usage = var_hash[var.name]
    var_usage.declared_by = node
    var_usage.path = context.path
  }
  super
  @variable_context_stack.pop
end

def on_variable_identifier(node, parent)

- assign its AST node
- mark the variable as used
For VariableIdentifiers:
def on_variable_identifier(node, parent)
  usage_context = @variable_context_stack.last
  declared_variables = @variable_usages_for_context[usage_context]
  usage = declared_variables[node.name]
  usage.used_by = usage_context
  usage.ast_node = node
  usage.path = context.path
  super
end