# TODO: Project Specific Lints
#
# 1. Anything that implements ASTNode must be suffixed by AST
# 2. Make sure {VariableDeclaration}AST because it's suffixed by AST implements VariableDeclaration

schema {
  query: RootSchemaQuery
}
directive @filter(
  """
  Name of the filter operation to perform.
  """
  op: String!
  """
  List of string operands for the operator.
  """
  value: [String!]
) on FIELD | INLINE_FRAGMENT
directive @tag(
  """
  Name to apply to the given property field.
  """
  name: String
) on FIELD
directive @output(
  """
  What to designate the output field generated from this property field.
  """
  name: String
) on FIELD
directive @optional on FIELD
directive @recurse(
  """
  Recurse up to this many times on this edge. A depth of 1 produces the current
  vertex and its immediate neighbors along the given edge.
  """
  depth: Int!
) on FIELD
directive @fold on FIELD
directive @transform(
  """
  Name of the transformation operation to perform.
  """
  op: String!
) on FIELD

"""
All the possible data types where querying can begin in this API.
"""
type RootSchemaQuery {
  File: [File!]!
}

type File {
  last_path_part: PathPart!
  path_part: [PathPart!]!

  class: [ClassAST!]!
  type_annotation: [TypeAnnotationAST!]!
  interface: [InterfaceAST!]!
  jsx_element: [JSXElementAST!]!
  import: [ImportAST!]!
  variable_declaration: [VariableDeclarationAST!]!

  ast_node: [ASTNode!]!
}

type PathPart {
  name: String!
  is_first: Boolean!
  is_last: Boolean!

  next: PathPart
  prev: PathPart
}

type SpecificImport {
  """
  Name in original file
  """
  original_name: String!
  """
  Aliased name, in "import {X as Y} from 'source'", this would be Y, but in "import {A} from 'source'" this owuld be A
  """
  local_name: String!
  span: Span!
}

type DefaultImport {
  local_name: String!
  span: Span!
}

interface Import {
  from_path: String!
  entire_span: Span!

  """
  Only gives specific imports, not default imports or (import *)'s
  """
  specific_import: [SpecificImport!]!

  default_import: [DefaultImport!]!
}

interface AssignmentType implements HasSpan {
  """
  if the left side has exactly one name. No destructuring.
  ie: const apple = 1; let orange = 'a'; var blue;
  """
  assignment_to_variable_name: String
  span: Span!
}

"""
A single VariableDeclarator. (const/let/var)!
If you have multiple variables assigned at once with commas,
this will fire once per assignment.
"""
interface VariableDeclaration implements HasSpan {
  left: AssignmentType!
  # HasSpan
  span: Span!
}

type SearchParameter {
  key: String!
  value: String!
}

type URL {
  search_parameter: [SearchParameter!]!
}

interface JSXAttribute {
  name: String!
  """
  Only non-null if the string can be trivially coerced to a constant string
  ie <Fruit blueberry="blue" />, <Fruit blueberry={"blue"} />, and <Fruit blueberry={`blue`} />
  """
  value_as_constant_string: String

  """
  If the value is an expression
  ie: <Fruit blueberry={"blue"} /> or <Fruit blueberry={{color: "blue"}} />
  """
  value_as_expression: Expression

  """
  If value_as_constant_string is not null, then this will try to parse that to a url
  """
  value_as_url: URL

  span: Span!
}

type JSXSpreadAttribute {
  span: Span!
}

type JSXOpeningElement {
  """
  Smallest equivalent, removes spaces
  """
  name: String!
  """
  non-spread attributes
  """
  attribute: [JSXAttribute!]!
  spread_attribute: [JSXSpreadAttribute!]!

  attribute_count: Int!

  span: Span!
}

"""
interface A extends B.C {}
B.C is always more than one identifier.
"""
type MemberExtend implements InterfaceExtend {
  span: Span!
}

"""
interface A extends B {}
B is always just one identifier.
"""
type SimpleExtend implements InterfaceExtend {
  span: Span!
}

interface InterfaceExtend {
  span: Span!
}

interface Interface {
  name: String!
  extend: [InterfaceExtend!]!
  name_span: Span!
  entire_span: Span!
}

type ClassProperty {
  is_abstract: Boolean!
  """
  public | private | protected
  """
  accessibility: String
  span: Span!
}

type ClassMethod {
  is_abstract: Boolean!
  """
  public | private | protected
  """
  accessibility: String
  span: Span!
}

interface Class {
  name: String
  is_abstract: Boolean!

  """
  if the extended expression is an identifier, this will give you the name
  """
  extended_class_name: String

  constructor: [ClassMethod!]!
  method: [ClassMethod!]!
  getter: [ClassMethod!]!
  setter: [ClassMethod!]!
  property: [ClassProperty!]!
  # TODO: some way to fall back from name_span to entire_class span
  """
  Doesn't exist on anonymous classes
  """
  name_span: Span
  entire_class_span: Span!
}

interface Type {
  span: Span!
}

interface TypeAnnotation implements HasSpan {
  type: Type!
  span: Span!
}

type Span {
  start: Int!
  end: Int!
  str: String!
}

type JSXSpreadChild {
  span: Span!
}

type JSXExpressionContainer {
  span: Span!
}

type JSXFragment {
  span: Span!
}

type JSXText {
  text: String!
  span: Span!
}

"""
If the number has a negative sign before it, it will not be a `NumberLiteral`
"""
interface NumberLiteral implements HasSpan & Expression {
  span: Span!
  """
  Nullable if the number is NaN
  """
  number: Float

  # Expression
  as_constant_string: String
  strip_parens: Expression!
}

type NumberLiteralAST implements HasSpan & ASTNode & Expression & NumberLiteral {
  span: Span!
  """
  Nullable if the number is NaN
  """
  number: Float
  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
  # Expression
  as_constant_string: String
  strip_parens: Expression!
}

# Expression

interface JSXElement implements Expression & HasSpan {
  opening_element: JSXOpeningElement!
  """
  There are many types of children,
  however this will only yield JSXElement children
  """
  child_element: [JSXElement!]!
  child_text: [JSXText!]!
  child_fragment: [JSXFragment!]!
  child_expression_container: [JSXExpressionContainer!]!
  child_spread: [JSXSpreadChild!]!
  child_count: Int!
  # Expression
  as_constant_string: String
  strip_parens: Expression!
  # HasSpan
  span: Span!
}

interface ObjectProperty implements HasSpan {
  # HasSpan
  span: Span!
}

interface SpreadIntoObject implements ObjectProperty & HasSpan {
  value: Expression!

  # HasSpan
  span: Span!
}

type SpreadIntoObjectAST implements ASTNode & SpreadIntoObject & ObjectProperty & HasSpan {
  value: Expression!

  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!

  # HasSpan
  span: Span!
}

interface ObjectEntry implements ObjectProperty & HasSpan {
  key: Expression!
  value: Expression!

  # HasSpan
  span: Span!
}

type ObjectEntryAST implements ASTNode & ObjectEntry & ObjectProperty & HasSpan {
  key: Expression!
  value: Expression!

  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!

  # HasSpan
  span: Span!
}

interface ObjectLiteral implements Expression & HasSpan {
  value(key: String!): Expression
  entry: [ObjectProperty!]!

  # Expression
  as_constant_string: String
  strip_parens: Expression!
  # HasSpan
  span: Span!
}

type ObjectLiteralAST implements ObjectLiteral & Expression & HasSpan & ASTNode {
  value(key: String!): Expression
  entry: [ObjectProperty!]!

  # Expression
  as_constant_string: String
  strip_parens: Expression!
  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
  # HasSpan
  span: Span!
}

interface Argument {
  span: Span!
}

interface FnCall implements Expression & HasSpan {
  callee: Expression!
  arguments: [Argument!]!

  # Expression
  as_constant_string: String
  strip_parens: Expression!
  # HasSpan
  span: Span!
}

type FnCallAST implements FnCall & Expression & HasSpan & ASTNode {
  callee: Expression!
  arguments: [Argument!]!

  # Expression
  as_constant_string: String
  strip_parens: Expression!
  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
  # HasSpan
  span: Span!
}

"""
Any assignment that isn't a variable declaration, which means no const/let/var.
ie: "a = 1;", "a /= 1;", "a += 1;", etc.
"""
interface Reassignment implements HasSpan & Expression {
  """
  Only nonnull if the left is not an object or array destructuring.
  examples that this would be usable: "a = 12;", "a.b = 'apple';"
  """
  left_as_expression: Expression
  right: Expression!
  # Expression
  as_constant_string: String
  strip_parens: Expression!
  # HasSpan
  span: Span!
}

"""
Any assignment that isn't a variable declaration, which means no const/let/var.
ie: "a = 1;", "a /= 1;", "a += 1;", etc.
"""
type ReassignmentAST implements HasSpan & ASTNode & Reassignment & Expression {
  """
  Only nonnull if the left is not an object or array destructuring.
  examples that this would be usable: "a = 12;", "a.b = 'apple';"
  """
  left_as_expression: Expression
  right: Expression!
  # Expression
  as_constant_string: String
  strip_parens: Expression!
  # HasSpan
  span: Span!
  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
}

interface Statement implements HasSpan {
  # HasSpan
  span: Span!
}

interface FunctionBody implements HasSpan {
  statement: [Statement!]!

  # HasSpan
  span: Span!
}

type FunctionBodyAST implements HasSpan & ASTNode & FunctionBody {
  statement: [Statement!]!

  # HasSpan
  span: Span!
  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
}

interface ArrowFunction implements HasSpan {
  # HasSpan
  span: Span!
}

type ArrowFunctionAST implements HasSpan & ArrowFunction & ASTNode {
  # HasSpan
  span: Span!
  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
}

"""
A function defined in the sourcecode.
"""
interface FnDeclaration implements HasSpan {
  """
  If this function is used an an expression, the name can be omitted.
  ie: "const a = function() {};"
  """
  name: String

  # HasSpan
  span: Span!
}

"""
A function defined in the sourcecode.
"""
type FnDeclarationAST implements ASTNode & FnDeclaration & HasSpan {
  name: String

  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
  # HasSpan
  span: Span!
}

interface Name implements HasSpan {
  name: String!
  # HasSpan
  span: Span!
}

type NameAST implements ASTNode & Name & HasSpan {
  name: String!
  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
  # HasSpan
  span: Span!
}

interface DotProperty implements HasSpan & Expression {
  """
  In "data.user.name", this would be "data.user"
  """
  called_on: Expression!
  """
  In "data.user.name", this would be "name"
  """
  accessed_property: Name!
  # Expression
  as_constant_string: String
  strip_parens: Expression!
  # HasSpan
  span: Span!
}

type DotPropertyAST implements DotProperty & ASTNode & HasSpan & Expression {
  """
  In "data.user.name", this would be "data.user"
  """
  called_on: Expression!
  """
  In "data.user.name", this would be "name"
  """
  accessed_property: Name!
  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
  # Expression
  as_constant_string: String
  strip_parens: Expression!
  # HasSpan
  span: Span!
}

interface Expression implements HasSpan {
  """
  Only non-null if the string can be trivially coerced to a constant string
  ie: const a = "apple"; const b = `blueberry`
  """
  as_constant_string: String
  strip_parens: Expression!

  # HasSpan
  span: Span!
}

# ASTNode

"""
A single VariableDeclarator. (const/let/var)!
If you have multiple variables assigned at once with commas,
this will fire once per assignment.
"""
type VariableDeclarationAST implements VariableDeclaration & ASTNode & HasSpan {
  left: AssignmentType!
  right: Expression
  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
  # HasSpan
  span: Span!
}

type TypeAnnotationAST implements TypeAnnotation & ASTNode & HasSpan {
  type: Type!
  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
  # HasSpan
  span: Span!
}

type InterfaceAST implements Interface & ASTNode & HasSpan {
  name: String!
  extend: [InterfaceExtend!]!
  name_span: Span!
  entire_span: Span!
  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
  # HasSpan
  span: Span!
}

type ImportAST implements Import & ASTNode & HasSpan {
  from_path: String!
  entire_span: Span!

  """
  Only gives specific imports, not default imports or (import *)'s
  """
  specific_import: [SpecificImport!]!

  default_import: [DefaultImport!]!

  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
  # HasSpan
  span: Span!
}

type JSXElementAST implements JSXElement & Expression & ASTNode & HasSpan {
  opening_element: JSXOpeningElement!
  """
  There are many types of children,
  however this will only yield JSXElement children
  """
  child_element: [JSXElement!]!
  child_text: [JSXText!]!
  child_fragment: [JSXFragment!]!
  child_expression_container: [JSXExpressionContainer!]!
  child_spread: [JSXSpreadChild!]!
  child_count: Int!
  # Expression
  as_constant_string: String
  strip_parens: Expression!
  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!
  # HasSpan
  span: Span!
}

type ClassAST implements ASTNode & Class & HasSpan {
  name: String
  is_abstract: Boolean!

  """
  if the extended expression is an identifier, this will give you the name
  """
  extended_class_name: String

  constructor: [ClassMethod!]!
  method: [ClassMethod!]!
  getter: [ClassMethod!]!
  setter: [ClassMethod!]!
  property: [ClassProperty!]!
  # TODO: some way to fall back from name_span to entire_class span
  """
  Doesn't exist on anonymous classes
  """
  name_span: Span
  entire_class_span: Span!

  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!

  # HasSpan
  span: Span!
}

type IfStatementAST implements ASTNode & HasSpan {
  condition: Expression!

  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!

  # HasSpan
  span: Span!
}

type ReturnStatement implements Statement & HasSpan {
  expression: Expression

  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!

  # HasSpan
  span: Span!
}

type ReturnAST implements ASTNode & HasSpan {
  expression: Expression

  # ASTNode
  parent: ASTNode
  ancestor: [ASTNode!]!

  # HasSpan
  span: Span!
}

# MAKE SURE TO KEEP ANCESTOR&CHILD IN SYNC
interface ASTNode implements HasSpan {
  parent: ASTNode
  ancestor: [ASTNode!]!
  span: Span!
}

interface HasSpan {
  span: Span!
}
