/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::ArgumentName;
use common::Diagnostic;
use common::DiagnosticsResult;
use common::NamedItem;
use errors::validate;
use errors::validate_map;
use graphql_ir::Argument;
use graphql_ir::ConstantValue;
use graphql_ir::Directive;
use graphql_ir::LinkedField;
use graphql_ir::Program;
use graphql_ir::Selection;
use graphql_ir::ValidationMessage;
use graphql_ir::Validator;
use graphql_ir::Value;
use intern::string_key::StringKey;
use intern::Lookup;
use schema::Field;
use schema::Schema;
use schema::Type;
use schema::TypeReference;

use crate::connections::extract_connection_directive;
use crate::connections::ConnectionConstants;
use crate::connections::ConnectionInterface;
use crate::handle_fields::extract_handle_field_directive_args_for_connection;
use crate::handle_fields::CONNECTION_HANDLER_ARG_NAME;
use crate::handle_fields::DYNAMIC_KEY_ARG_NAME;
use crate::handle_fields::FILTERS_ARG_NAME;
use crate::handle_fields::KEY_ARG_NAME;

pub fn validate_connections(
    program: &Program,
    connection_interface: &ConnectionInterface,
) -> DiagnosticsResult<()> {
    let mut validator = ConnectionValidation::new(program, connection_interface);
    validator.validate_program(program)
}
struct ConnectionValidation<'s> {
    connection_constants: ConnectionConstants,
    connection_interface: &'s ConnectionInterface,
    program: &'s Program,
}

impl<'s> ConnectionValidation<'s> {
    fn new(program: &'s Program, connection_interface: &'s ConnectionInterface) -> Self {
        Self {
            connection_constants: ConnectionConstants::default(),
            connection_interface,
            program,
        }
    }

    /// Validates that the connection field is a non-plural, object or interface type.
    fn validate_connection_field_type(
        &self,
        connection_field: &LinkedField,
        connection_schema_field: &Field,
        connection_directive: &Directive,
    ) -> DiagnosticsResult<Type> {
        let schema = &self.program.schema;
        let field_type = connection_schema_field.type_.nullable_type();
        if field_type.is_list() || !field_type.inner().is_object_or_interface() {
            return Err(vec![Diagnostic::error(
                ValidationMessage::InvalidConnectionFieldType {
                    connection_directive_name: connection_directive.name.item,
                    connection_field_name: connection_schema_field.name.item,
                    connection_type_string: schema.get_type_string(field_type),
                },
                connection_field.definition.location,
            )]);
        }
        Ok(field_type.inner())
    }

    /// Validates that the selection is a valid connection:
    /// - Specifies a first or last argument to prevent accidental, unconstrained
    ///   data access.
    /// - Has an `edges` selection, otherwise there is nothing to paginate.
    ///
    /// TODO: This implementation requires the edges field to be a direct selection
    /// and not contained within an inline fragment or fragment spread. It's
    /// technically possible to remove this restriction if this pattern becomes
    /// common/necessary.
    fn validate_connection_selection(
        &self,
        connection_field: &'s LinkedField,
        connection_schema_field: &'s Field,
    ) -> DiagnosticsResult<&'s LinkedField> {
        let schema = &self.program.schema;

        let first_arg = connection_field
            .arguments
            .named(self.connection_constants.first_arg_name);
        let last_arg = connection_field
            .arguments
            .named(self.connection_constants.last_arg_name);
        if first_arg.is_none() && last_arg.is_none() {
            return Err(vec![Diagnostic::error(
                ValidationMessage::ExpectedConnectionToHaveCountArgs {
                    connection_field_name: connection_schema_field.name.item,
                    first_arg: self.connection_constants.first_arg_name,
                    last_arg: self.connection_constants.last_arg_name,
                },
                connection_field.definition.location,
            )]);
        }

        let edges_selection_name = self.connection_interface.edges;
        let edges_selection: Option<&Selection> =
            connection_field.selections.iter().find(|sel| match sel {
                Selection::LinkedField(field) => {
                    schema.field(field.definition.item).name.item == edges_selection_name
                }
                _ => false,
            });

        if let Some(edges_selection) = edges_selection {
            if let Selection::LinkedField(edges_field) = edges_selection {
                Ok(edges_field.as_ref())
            } else {
                unreachable!("Expected selection for edges to be a linked field.")
            }
        } else {
            Err(vec![Diagnostic::error(
                ValidationMessage::ExpectedConnectionToHaveEdgesSelection {
                    connection_field_name: connection_schema_field.name.item,
                    edges_selection_name,
                },
                connection_field.definition.location,
            )])
        }
    }

    /// Validates that the type satisfies the Connection specification:
    /// - The type has a valid edges field, which returns a list objects that should each expose:
    ///   - a scalar `cursor` field
    ///   - a object `node` field
    /// - The type has a page info field which is an object with the correct
    ///   subfields.
    fn validate_connection_spec(
        &self,
        connection_field: &LinkedField,
        connection_schema_field: &Field,
        connection_field_type: Type,
        connection_directive: &Directive,
        edges_field: &LinkedField,
    ) -> DiagnosticsResult<()> {
        validate!(
            self.validate_edges_spec(
                connection_field,
                connection_schema_field,
                connection_field_type,
                connection_directive,
                edges_field,
            ),
            self.validate_page_info_spec(
                connection_field,
                connection_schema_field,
                connection_field_type,
                connection_directive,
            )
        )
    }

    /// Validates that the Connection type has a valid `edges` field, which returns a list objects
    /// that should each expose:
    /// - a scalar `cursor` field
    /// - a object `node` field
    fn validate_edges_spec(
        &self,
        connection_field: &LinkedField,
        connection_schema_field: &Field,
        connection_field_type: Type,
        connection_directive: &Directive,
        edges_field: &LinkedField,
    ) -> DiagnosticsResult<()> {
        let schema = &self.program.schema;
        let connection_directive_name = connection_directive.name.item;
        let connection_type_name = schema.get_type_name(connection_field_type);
        let connection_field_name = connection_schema_field.name.item;
        let edges_selection_name = self.connection_interface.edges;

        // Validate edges selection
        let (_, edges_type) = self.validate_selection(
            connection_field_type,
            edges_selection_name,
            |_, edges_type| edges_type.is_list() && edges_type.inner().is_object_or_interface(),
            || {
                vec![
                    Diagnostic::error(
                        ValidationMessage::ExpectedConnectionToExposeValidEdgesField {
                            connection_directive_name,
                            connection_field_name,
                            connection_type_name,
                            edges_selection_name,
                        },
                        connection_field.definition.location,
                    )
                    .annotate("invalid field type", edges_field.definition.location),
                ]
            },
        )?;

        let edge_type = edges_type.inner();
        let node_selection_name = self.connection_interface.node;
        let cursor_selection_name = self.connection_interface.cursor;
        validate!(
            // Validate edges.node selection
            self.validate_selection(
                edge_type,
                node_selection_name,
                |_, node_type| {
                    !node_type.is_list()
                        && (node_type.inner().is_abstract_type() || node_type.inner().is_object())
                },
                || {
                    vec![
                        Diagnostic::error(
                            ValidationMessage::ExpectedConnectionToExposeValidNodeField {
                                connection_directive_name,
                                connection_field_name,
                                connection_type_name,
                                edges_selection_name,
                                node_selection_name,
                            },
                            connection_field.definition.location,
                        )
                        .annotate("field with invalid type", edges_field.definition.location),
                    ]
                },
            ),
            // Validate edges.cursor selection
            self.validate_selection(
                edge_type,
                cursor_selection_name,
                |_, cursor_type| !cursor_type.is_list() && cursor_type.inner().is_scalar(),
                || {
                    vec![
                        Diagnostic::error(
                            ValidationMessage::ExpectedConnectionToExposeValidCursorField {
                                connection_directive_name,
                                connection_field_name,
                                connection_type_name,
                                cursor_selection_name,
                                edges_selection_name,
                            },
                            connection_field.definition.location,
                        )
                        .annotate("field with invalid type", edges_field.definition.location),
                    ]
                },
            )
        )
    }

    /// Validates that the Connection type has a valid `page_info` field,
    /// which should be an object that exposes the correct sub fields as scalars.
    fn validate_page_info_spec(
        &self,
        connection_field: &LinkedField,
        connection_schema_field: &Field,
        connection_field_type: Type,
        connection_directive: &Directive,
    ) -> DiagnosticsResult<()> {
        let schema = &self.program.schema;
        let connection_directive_name = connection_directive.name.item;
        let connection_type_name = schema.get_type_name(connection_field_type);
        let connection_field_name = connection_schema_field.name.item;
        let page_info_selection_name = self.connection_interface.page_info;

        // Validate page_info selection
        let (_, page_info_type) = self.validate_selection(
            connection_field_type,
            page_info_selection_name,
            |_, page_info_type| !page_info_type.is_list() && page_info_type.inner().is_object(),
            || {
                vec![Diagnostic::error(
                    ValidationMessage::ExpectedConnectionToExposeValidPageInfoField {
                        connection_directive_name,
                        connection_field_name,
                        connection_type_name,
                        page_info_selection_name,
                    },
                    connection_field.definition.location,
                )]
            },
        )?;

        let page_info_type = page_info_type.inner();
        let page_info_sub_fields = vec![
            self.connection_interface.end_cursor,
            self.connection_interface.has_next_page,
            self.connection_interface.has_previous_page,
            self.connection_interface.start_cursor,
        ];

        validate_map(page_info_sub_fields.iter(), |page_info_sub_field_name| {
            self.validate_selection(
                page_info_type,
                *page_info_sub_field_name,
                |_, sub_field_type| !sub_field_type.is_list() && sub_field_type.inner().is_scalar(),
                || {
                    vec![Diagnostic::error(
                        ValidationMessage::ExpectedConnectionToExposeValidPageInfoSubField {
                            connection_directive_name,
                            connection_field_name,
                            connection_type_name,
                            page_info_selection_name,
                            page_info_sub_field_name: *page_info_sub_field_name,
                        },
                        connection_field.definition.location,
                    )]
                },
            )
        })
    }

    fn validate_selection(
        &self,
        parent_type: Type,
        selection_name: StringKey,
        is_valid: impl Fn(&Field, &TypeReference<Type>) -> bool,
        error: impl Fn() -> Vec<Diagnostic>,
    ) -> DiagnosticsResult<(&Field, &TypeReference<Type>)> {
        let schema = &self.program.schema;
        if let Some(field_id) = schema.named_field(parent_type, selection_name) {
            let field = schema.field(field_id);
            let field_type = field.type_.nullable_type();
            if is_valid(field, field_type) {
                Ok((field, field_type))
            } else {
                Err(error())
            }
        } else {
            Err(error())
        }
    }

    fn validate_connection_arguments(
        &self,
        connection_field: &LinkedField,
        connection_schema_field: &Field,
        connection_directive: &Directive,
    ) -> DiagnosticsResult<()> {
        let connection_directive_args =
            extract_handle_field_directive_args_for_connection(connection_directive);

        validate!(
            self.validate_handler_arg(
                connection_field,
                connection_schema_field,
                connection_directive,
                connection_directive_args.handler_arg,
            ),
            self.validate_key_arg(
                connection_field,
                connection_schema_field,
                connection_directive,
                connection_directive_args.key_arg,
            ),
            self.validate_filters_arg(
                connection_field,
                connection_schema_field,
                connection_directive,
                connection_directive_args.filters_arg,
            ),
            self.validate_dynamic_key_arg(
                connection_field,
                connection_schema_field,
                connection_directive,
                connection_directive_args.dynamic_key_arg,
            )
        )
    }

    fn validate_handler_arg(
        &self,
        connection_field: &LinkedField,
        connection_schema_field: &Field,
        connection_directive: &Directive,
        constant_handler_arg: Option<(&Argument, &ConstantValue)>,
    ) -> DiagnosticsResult<()> {
        if let Some((arg, handler_val)) = constant_handler_arg {
            match handler_val {
                ConstantValue::String(_) => {}
                _ => {
                    return Err(vec![
                        Diagnostic::error(
                            ValidationMessage::InvalidConnectionHandlerArg {
                                connection_directive_name: connection_directive.name.item,
                                connection_field_name: connection_schema_field.name.item,
                                handler_arg_name: *CONNECTION_HANDLER_ARG_NAME,
                            },
                            arg.value.location,
                        )
                        .annotate("on connection field", connection_field.definition.location),
                    ]);
                }
            }
        }
        Ok(())
    }

    fn validate_key_arg(
        &self,
        connection_field: &LinkedField,
        connection_schema_field: &Field,
        connection_directive: &Directive,
        constant_key_arg: Option<(&Argument, &ConstantValue)>,
    ) -> DiagnosticsResult<()> {
        match constant_key_arg {
            Some((arg, key_val)) => match key_val {
                ConstantValue::String(string_val) => {
                    let field_alias_or_name = match connection_field.alias {
                        Some(alias) => alias.item,
                        None => connection_schema_field.name.item,
                    };
                    let postfix = format!("_{}", field_alias_or_name);
                    if !string_val.lookup().ends_with(postfix.as_str()) {
                        return Err(vec![
                            Diagnostic::error(
                                ValidationMessage::InvalidConnectionKeyArgPostfix {
                                    connection_directive_name: connection_directive.name.item,
                                    connection_field_name: connection_schema_field.name.item,
                                    key_arg_name: *KEY_ARG_NAME,
                                    key_arg_value: *string_val,
                                    postfix,
                                },
                                arg.value.location,
                            )
                            .annotate("related location", connection_field.definition.location),
                        ]);
                    }
                }
                _ => {
                    return Err(vec![
                        Diagnostic::error(
                            ValidationMessage::InvalidConnectionKeyArg {
                                connection_directive_name: connection_directive.name.item,
                                connection_field_name: connection_schema_field.name.item,
                                key_arg_name: *KEY_ARG_NAME,
                            },
                            arg.value.location,
                        )
                        .annotate("related location", connection_field.definition.location),
                    ]);
                }
            },
            None => {
                return Err(vec![Diagnostic::error(
                    ValidationMessage::InvalidConnectionKeyArg {
                        connection_directive_name: connection_directive.name.item,
                        connection_field_name: connection_schema_field.name.item,
                        key_arg_name: *KEY_ARG_NAME,
                    },
                    connection_directive.name.location,
                )]);
            }
        }
        Ok(())
    }

    fn validate_filters_arg(
        &self,
        connection_field: &LinkedField,
        connection_schema_field: &Field,
        connection_directive: &Directive,
        constant_filters_arg: Option<(&Argument, &ConstantValue)>,
    ) -> DiagnosticsResult<()> {
        if let Some((arg, filters_val)) = constant_filters_arg {
            match filters_val {
                ConstantValue::List(list_val) => {
                    validate_map(list_val, |filter_val| {
                        if let ConstantValue::String(filter_val) = filter_val {
                            if connection_field
                                .arguments
                                .named(ArgumentName(*filter_val))
                                .is_none()
                            {
                                Err(vec![Diagnostic::error(
                                    ValidationMessage::InvalidConnectionFiltersArgNotAnArgument {
                                        connection_directive_name: connection_directive.name.item,
                                        connection_field_name: connection_schema_field.name.item,
                                        filters_arg_name: *FILTERS_ARG_NAME,
                                        invalid_name: *filter_val,
                                    },
                                    arg.value.location,
                                )])
                            } else {
                                Ok(())
                            }
                        } else {
                            Err(vec![Diagnostic::error(
                                ValidationMessage::InvalidConnectionFiltersArg {
                                    connection_directive_name: connection_directive.name.item,
                                    connection_field_name: connection_schema_field.name.item,
                                    filters_arg_name: *FILTERS_ARG_NAME,
                                },
                                arg.value.location,
                            )])
                        }
                    })?;
                }
                _ => {
                    return Err(vec![
                        Diagnostic::error(
                            ValidationMessage::InvalidConnectionFiltersArg {
                                connection_directive_name: connection_directive.name.item,
                                connection_field_name: connection_schema_field.name.item,
                                filters_arg_name: *FILTERS_ARG_NAME,
                            },
                            arg.value.location,
                        )
                        .annotate("related location", connection_field.definition.location),
                    ]);
                }
            }
        }
        Ok(())
    }

    fn validate_dynamic_key_arg(
        &self,
        connection_field: &LinkedField,
        connection_schema_field: &Field,
        connection_directive: &Directive,
        dynamic_key_arg: Option<(&Argument, &Value)>,
    ) -> DiagnosticsResult<()> {
        if let Some((dynamic_key_arg, value)) = dynamic_key_arg {
            match value {
                Value::Variable(_) => {}
                _ => {
                    return Err(vec![
                        Diagnostic::error(
                            ValidationMessage::InvalidConnectionDynamicKeyArg {
                                connection_directive_name: connection_directive.name.item,
                                connection_field_name: connection_schema_field.name.item,
                                dynamic_key_arg_name: *DYNAMIC_KEY_ARG_NAME,
                            },
                            dynamic_key_arg.value.location,
                        )
                        .annotate("related location", connection_field.definition.location),
                    ]);
                }
            }
        }
        Ok(())
    }

    fn validate_stream_connection(
        &self,
        edges_field: &LinkedField,
        connection_field: &LinkedField,
    ) -> DiagnosticsResult<()> {
        if edges_field.alias.is_some() {
            return Err(vec![Diagnostic::error(
                ValidationMessage::UnsupportedAliasingInStreamConnection {
                    field_name: self.connection_interface.edges,
                },
                edges_field.definition.location,
            )]);
        }

        let page_info_selection = connection_field
            .selections
            .iter()
            .find_map(|sel| match sel {
                Selection::LinkedField(field) => {
                    if self.program.schema.field(field.definition.item).name.item
                        == self.connection_interface.page_info
                    {
                        Some(field)
                    } else {
                        None
                    }
                }
                _ => None,
            });
        if let Some(page_info_selection) = page_info_selection {
            if page_info_selection.alias.is_some() {
                return Err(vec![Diagnostic::error(
                    ValidationMessage::UnsupportedAliasingInStreamConnection {
                        field_name: self.connection_interface.page_info,
                    },
                    page_info_selection.definition.location,
                )]);
            }
        }

        Ok(())
    }
}

impl<'s> Validator for ConnectionValidation<'s> {
    const NAME: &'static str = "ConnectionValidation";
    const VALIDATE_ARGUMENTS: bool = false;
    const VALIDATE_DIRECTIVES: bool = false;

    fn validate_linked_field(&mut self, field: &LinkedField) -> DiagnosticsResult<()> {
        if let Some(connection_directive) =
            extract_connection_directive(&field.directives, self.connection_constants)
        {
            let connection_schema_field = self.program.schema.field(field.definition.item);

            let connection_field_type = self.validate_connection_field_type(
                field,
                connection_schema_field,
                connection_directive,
            )?;
            let edges_field = self.validate_connection_selection(field, connection_schema_field)?;

            if connection_directive.name.item
                == self.connection_constants.stream_connection_directive_name
            {
                self.validate_stream_connection(edges_field, field)?;
            }

            validate!(
                self.validate_connection_spec(
                    field,
                    connection_schema_field,
                    connection_field_type,
                    connection_directive,
                    edges_field,
                ),
                self.validate_connection_arguments(
                    field,
                    connection_schema_field,
                    connection_directive,
                ),
                self.default_validate_linked_field(field)
            )
        } else {
            self.default_validate_linked_field(field)
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::DiagnosticsResult;
use common::NamedItem;
use graphql_ir::FragmentDefinition;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::ValidationMessage;
use graphql_ir::Validator;
use intern::Lookup;

use crate::root_variables::InferVariablesVisitor;
use crate::DIRECTIVE_SPLIT_OPERATION;

pub fn validate_global_variables(program: &Program) -> DiagnosticsResult<()> {
    ValidateGlobalVariables::new(program).validate_program(program)
}

pub struct ValidateGlobalVariables<'program> {
    visitor: InferVariablesVisitor<'program>,
}

impl<'program> ValidateGlobalVariables<'program> {
    fn new(program: &'program Program) -> Self {
        Self {
            visitor: InferVariablesVisitor::new(program),
        }
    }
}

/// Validates that all used arguments must be defined
impl Validator for ValidateGlobalVariables<'_> {
    const NAME: &'static str = "ValidateGlobalVariables";
    const VALIDATE_ARGUMENTS: bool = false;
    const VALIDATE_DIRECTIVES: bool = false;

    fn validate_operation(&mut self, operation: &OperationDefinition) -> DiagnosticsResult<()> {
        // Skip 3D normalization fragments
        if operation
            .directives
            .named(*DIRECTIVE_SPLIT_OPERATION)
            .is_some()
        {
            return Ok(());
        }
        let (variables, _) = self.visitor.infer_operation_variables(operation);

        let undefined_variables: Vec<_> = variables
            .values()
            .filter(|var| {
                !(operation
                    .variable_definitions
                    .iter()
                    .any(|def| def.name.item == var.name.item))
            })
            .collect();

        if !undefined_variables.is_empty() {
            let is_plural = undefined_variables.len() > 1;
            let mut locations = undefined_variables
                .iter()
                .map(|arg_def| arg_def.name.location);
            let mut error = Diagnostic::error(
                ValidationMessage::GlobalVariables {
                    operation_name: operation.name.item.0,
                    variables_string: format!(
                        "{}: '${}'",
                        if is_plural { "s" } else { "" },
                        undefined_variables
                            .iter()
                            .map(|var| var.name.item.0.lookup())
                            .collect::<Vec<_>>()
                            .join("', '$"),
                    ),
                },
                locations.next().unwrap(),
            );
            for related_location in locations {
                error = error.annotate("related location", related_location);
            }
            return Err(vec![error]);
        }
        Ok(())
    }

    fn validate_fragment(&mut self, _: &FragmentDefinition) -> DiagnosticsResult<()> {
        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::DiagnosticsResult;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::Validator;
use graphql_ir::VariableName;
use intern::Lookup;
use thiserror::Error;

use crate::util::INTERNAL_RELAY_VARIABLES_PREFIX;

pub fn validate_global_variable_names(program: &Program) -> DiagnosticsResult<()> {
    ValidateGlobalVariableNames {}.validate_program(program)
}

#[derive(Default)]
pub struct ValidateGlobalVariableNames {}

impl Validator for ValidateGlobalVariableNames {
    const NAME: &'static str = "ValidateGlobalVariableNames";
    const VALIDATE_ARGUMENTS: bool = false;
    const VALIDATE_DIRECTIVES: bool = false;

    fn validate_operation(&mut self, operation: &OperationDefinition) -> DiagnosticsResult<()> {
        let mut invalid_names = operation.variable_definitions.iter().filter_map(|def| {
            if def
                .name
                .item
                .0
                .lookup()
                .starts_with(INTERNAL_RELAY_VARIABLES_PREFIX.lookup())
            {
                Some(def.name)
            } else {
                None
            }
        });

        if let Some(first_name) = invalid_names.next() {
            let mut error = Diagnostic::error(
                ValidationMessage::InvalidOperationVariablePrefix(first_name.item),
                first_name.location,
            );

            for invalid_name in invalid_names {
                error = error.annotate("related location", invalid_name.location);
            }
            return Err(vec![error]);
        }
        Ok(())
    }
}

#[derive(Clone, Debug, Error, Eq, PartialEq, Ord, PartialOrd, Hash)]
enum ValidationMessage {
    #[error(
        "Invalid name `${0}` for an operation variable. The `__relay_internal` prefix is reserved for relay internal usage."
    )]
    InvalidOperationVariablePrefix(VariableName),
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::DiagnosticsResult;
use common::WithLocation;
use errors::validate;
use graphql_ir::LinkedField;
use graphql_ir::Program;
use graphql_ir::ScalarField;
use graphql_ir::ValidationMessage;
use graphql_ir::Validator;
use intern::string_key::StringKey;
use intern::Lookup;

pub fn validate_no_double_underscore_alias(program: &Program) -> DiagnosticsResult<()> {
    let mut validator = ValidateNoDoubleUnderscoreAlias {};
    validator.validate_program(program)
}

struct ValidateNoDoubleUnderscoreAlias {}

impl Validator for ValidateNoDoubleUnderscoreAlias {
    const NAME: &'static str = "ValidateNoDoubleUnderscoreAlias";
    const VALIDATE_ARGUMENTS: bool = false;
    const VALIDATE_DIRECTIVES: bool = false;

    fn validate_linked_field(&mut self, field: &LinkedField) -> DiagnosticsResult<()> {
        validate!(
            if let Some(alias) = field.alias {
                validate_field_alias(&alias).map_err(|x| vec![x])
            } else {
                Ok(())
            },
            self.validate_selections(&field.selections)
        )
    }

    fn validate_scalar_field(&mut self, field: &ScalarField) -> DiagnosticsResult<()> {
        if let Some(alias) = field.alias {
            validate_field_alias(&alias).map_err(|x| vec![x])
        } else {
            Ok(())
        }
    }
}

fn validate_field_alias(alias: &WithLocation<StringKey>) -> Result<(), Diagnostic> {
    if alias.item.lookup().starts_with("__") {
        return Err(Diagnostic::error(
            ValidationMessage::NoDoubleUnderscoreAlias,
            alias.location,
        ));
    }
    Ok(())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::DiagnosticsResult;
use common::DirectiveName;
use common::Location;
use common::NamedItem;
use common::SourceLocationKey;
use common::Span;
use errors::validate_map;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentDefinitionNameSet;
use graphql_ir::FragmentSpread;
use graphql_ir::Program;
use graphql_ir::ValidationMessage;
use graphql_ir::Validator;

use crate::no_inline::is_raw_response_type_enabled;
use crate::no_inline::NO_INLINE_DIRECTIVE_NAME;
use crate::no_inline::RAW_RESPONSE_TYPE_NAME;

/// To generate full raw response types, we need to also generate raw response types for
/// @no_inline fragment normalization files. So raw_response_type argument is required
/// on these @no_inline directive.
pub fn validate_no_inline_fragments_with_raw_response_type(
    program: &Program,
) -> DiagnosticsResult<()> {
    let mut validator = NoInlineRawResponseTypeValidator::new(program);
    validator.validate_program(program)
}

struct NoInlineRawResponseTypeValidator<'a> {
    validated: FragmentDefinitionNameSet,
    program: &'a Program,
    current_query_location: Location,
}

impl<'a> NoInlineRawResponseTypeValidator<'a> {
    fn new(program: &'a Program) -> Self {
        Self {
            validated: Default::default(),
            current_query_location: Location::new(SourceLocationKey::generated(), Span::empty()),
            program,
        }
    }
}

impl<'a> Validator for NoInlineRawResponseTypeValidator<'a> {
    const NAME: &'static str = "NoInlineRawResponseTypeValidator";
    const VALIDATE_ARGUMENTS: bool = false;
    const VALIDATE_DIRECTIVES: bool = false;

    fn validate_program(&mut self, program: &Program) -> DiagnosticsResult<()> {
        validate_map(program.operations(), |operation| {
            if operation
                .directives
                .named(DirectiveName(*RAW_RESPONSE_TYPE_NAME))
                .is_some()
            {
                self.current_query_location = operation.name.location;
                self.default_validate_operation(operation)
            } else {
                Ok(())
            }
        })
    }

    fn validate_fragment(&mut self, fragment: &FragmentDefinition) -> DiagnosticsResult<()> {
        if let Some(directive) = fragment.directives.named(*NO_INLINE_DIRECTIVE_NAME) {
            if !is_raw_response_type_enabled(directive) {
                return Err(vec![
                    Diagnostic::error(
                        ValidationMessage::RequiredRawResponseTypeOnNoInline {
                            fragment_name: fragment.name.item,
                        },
                        fragment.name.location,
                    )
                    .annotate(
                        "The query with @raw_response_type",
                        self.current_query_location,
                    ),
                ]);
            }
        }
        self.default_validate_fragment(fragment)
    }

    fn validate_fragment_spread(&mut self, spread: &FragmentSpread) -> DiagnosticsResult<()> {
        if self.validated.contains(&spread.fragment.item) {
            return Ok(());
        }
        self.validated.insert(spread.fragment.item);
        let fragment = self.program.fragment(spread.fragment.item).unwrap();
        self.validate_fragment(fragment)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::sync::Arc;

use common::ArgumentName;
use common::Diagnostic;
use common::DiagnosticsResult;
use common::NamedItem;
use common::WithLocation;
use graphql_ir::LinkedField;
use graphql_ir::Program;
use graphql_ir::ScalarField;
use graphql_ir::Validator;
use intern::string_key::Intern;
use lazy_static::lazy_static;
use relay_config::SchemaConfig;
use schema::FieldID;
use schema::SDLSchema;
use schema::Schema;

use crate::ValidationMessage;

lazy_static! {
    static ref ARGUMENT_REASON: ArgumentName = ArgumentName("reason".intern());
}

pub fn validate_no_unselectable_selections(
    program: &Program,
    schema_config: &SchemaConfig,
) -> DiagnosticsResult<()> {
    let mut validator = UnselectableSelections::new(&program.schema, schema_config);
    validator.validate_program(program)?;

    if validator.errors.is_empty() {
        Ok(())
    } else {
        Err(validator.errors)
    }
}

// This validates that all linked and scalar fields aren't marked as unselectable.
// Unselectable fields cannot be selected for any reason (including no reason at all!).
#[derive(Debug)]
struct UnselectableSelections<'a, 'b> {
    schema: &'a Arc<SDLSchema>,
    schema_config: &'b SchemaConfig,
    errors: Vec<Diagnostic>,
}

impl<'a, 'b> UnselectableSelections<'a, 'b> {
    fn new(schema: &'a Arc<SDLSchema>, schema_config: &'b SchemaConfig) -> Self {
        Self {
            schema,
            schema_config,
            errors: vec![],
        }
    }

    fn validate_field(&mut self, field_id: &WithLocation<FieldID>) {
        let schema = &self.schema;
        let field_definition = schema.field(field_id.item);

        if let Some(directive) = field_definition
            .directives
            .named(self.schema_config.unselectable_directive_name)
        {
            let reason = directive
                .arguments
                .named(*ARGUMENT_REASON)
                .and_then(|arg| arg.value.get_string_literal());

            let parent_name = field_definition
                .parent_type
                .map(|parent_type| schema.get_type_name(parent_type));

            self.errors.push(Diagnostic::error(
                ValidationMessage::UnselectableField {
                    field_name: field_definition.name.item,
                    parent_name,
                    reason,
                },
                field_id.location,
            ));
        }
    }
}

impl<'a, 'b> Validator for UnselectableSelections<'a, 'b> {
    const NAME: &'static str = "UnselectableSelections";
    const VALIDATE_ARGUMENTS: bool = false;
    const VALIDATE_DIRECTIVES: bool = false;

    fn validate_linked_field(&mut self, field: &LinkedField) -> DiagnosticsResult<()> {
        self.validate_field(&field.definition);
        self.default_validate_linked_field(field)
    }

    fn validate_scalar_field(&mut self, field: &ScalarField) -> DiagnosticsResult<()> {
        self.validate_field(&field.definition);
        self.default_validate_scalar_field(field)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::collections::HashMap;

use common::Diagnostic;
use common::DiagnosticsResult;
use common::NamedItem;
use errors::validate;
use graphql_ir::ConstantValue;
use graphql_ir::Directive;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentSpread;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::ValidationMessage;
use graphql_ir::Validator;
use graphql_ir::Value;
use graphql_ir::VariableDefinition;
use graphql_ir::VariableName;
use schema::Schema;

use crate::relay_directive::MASK_ARG_NAME;
use crate::relay_directive::PLURAL_ARG_NAME;
use crate::relay_directive::RELAY_DIRECTIVE_NAME;
use crate::should_generate_hack_preloader;

pub fn validate_relay_directives(program: &Program) -> DiagnosticsResult<()> {
    let mut validator = RelayDirectiveValidation::new(program);
    validator.validate_program(program)
}

enum ArgumentDefinition<'ir> {
    Global(&'ir VariableDefinition),
    Local(&'ir VariableDefinition),
}

// This validates both @relay(plural) and @relay(mask) usages
struct RelayDirectiveValidation<'program> {
    program: &'program Program,
    // TODO(T63626938): This assumes that each document is processed serially (not in parallel or concurrently)
    current_reachable_arguments: Vec<&'program VariableDefinition>,
}

fn find_relay_directive(directives: &[Directive]) -> Option<&Directive> {
    directives.named(*RELAY_DIRECTIVE_NAME)
}

impl<'program> RelayDirectiveValidation<'program> {
    fn new(program: &'program Program) -> Self {
        Self {
            program,
            current_reachable_arguments: Default::default(),
        }
    }

    /// For ...Fragment @relay(mask:false), disallow fragments with directives or local variables
    fn validate_unmask_fragment_spread(
        &mut self,
        spread: &FragmentSpread,
    ) -> DiagnosticsResult<()> {
        let mut errs = vec![];
        let fragment = self.program.fragment(spread.fragment.item).unwrap();
        if !(fragment.directives.is_empty()
            || fragment.directives.len() == 1
                && fragment.directives[0].name.item == *RELAY_DIRECTIVE_NAME)
        {
            errs.push(
                Diagnostic::error(
                    ValidationMessage::InvalidUnmaskOnFragmentWithDirectives,
                    spread.fragment.location,
                )
                .annotate("related location", fragment.name.location),
            )
        }

        self.current_reachable_arguments
            .extend(&fragment.used_global_variables);
        if errs.is_empty() { Ok(()) } else { Err(errs) }
    }

    /// For all reachable arguments in the unmaksed fragments, validate that for the variables with the same name:
    /// 1. They are either all global argument or all local arguments
    /// 2. Their types should be same, or one is the subset of the
    fn validate_reachable_arguments(
        &self,
        map: &mut HashMap<VariableName, ArgumentDefinition<'program>>,
    ) -> DiagnosticsResult<()> {
        let mut errs = vec![];
        for arg in &self.current_reachable_arguments {
            if let Some(prev_arg) = map.get(&arg.name.item) {
                match prev_arg {
                    ArgumentDefinition::Local(prev_arg) => errs.push(
                        Diagnostic::error(
                            ValidationMessage::InvalidUnmaskOnLocalAndGloablVariablesWithSameName,
                            prev_arg.name.location,
                        )
                        .annotate("related location", arg.name.location),
                    ),
                    ArgumentDefinition::Global(prev_arg) => {
                        if !self
                            .program
                            .schema
                            .is_type_subtype_of(&prev_arg.type_, &arg.type_)
                            && !self
                                .program
                                .schema
                                .is_type_subtype_of(&arg.type_, &prev_arg.type_)
                        {
                            errs.push(Diagnostic::error(
                                ValidationMessage::InvalidUnmaskOnVariablesOfIncompatibleTypesWithSameName{
                                    prev_arg_type: self.program.schema.get_type_string(&prev_arg.type_),
                                    next_arg_type: self.program.schema.get_type_string(&arg.type_),
                                },
                                prev_arg.name.location,
                            ).annotate("related location", arg.name.location))
                        }
                    }
                }
            } else {
                map.insert(arg.name.item, ArgumentDefinition::Global(arg));
            }
        }
        if errs.is_empty() { Ok(()) } else { Err(errs) }
    }

    fn validate_relay_directives(&self, directives: &[Directive]) -> DiagnosticsResult<()> {
        let mut errs = vec![];
        if let Some(directive) = find_relay_directive(directives) {
            for arg in &directive.arguments {
                if arg.name.item == *PLURAL_ARG_NAME || arg.name.item == *MASK_ARG_NAME {
                    match arg.value.item {
                        Value::Constant(ConstantValue::Boolean(_))
                        | Value::Constant(ConstantValue::Null()) => {}
                        _ => errs.push(Diagnostic::error(
                            ValidationMessage::InvalidRelayDirectiveArg(arg.name.item),
                            arg.value.location,
                        )),
                    }
                }
            }
        }
        if errs.is_empty() { Ok(()) } else { Err(errs) }
    }
}

impl Validator for RelayDirectiveValidation<'_> {
    const NAME: &'static str = "RelayDirectiveValidation";
    const VALIDATE_ARGUMENTS: bool = false;
    const VALIDATE_DIRECTIVES: bool = false;

    fn validate_fragment(&mut self, fragment: &FragmentDefinition) -> DiagnosticsResult<()> {
        // Initialize arguments state for @relay(mask: false),
        self.current_reachable_arguments = Default::default();
        validate!(
            self.default_validate_fragment(fragment),
            self.validate_relay_directives(&fragment.directives),
            if self.current_reachable_arguments.is_empty() {
                Ok(())
            } else {
                let mut map: HashMap<VariableName, _> = Default::default();
                for variable in &fragment.used_global_variables {
                    map.insert(variable.name.item, ArgumentDefinition::Global(variable));
                }
                for variable in &fragment.variable_definitions {
                    map.insert(variable.name.item, ArgumentDefinition::Local(variable));
                }
                self.validate_reachable_arguments(&mut map)
            }
        )
    }

    fn validate_operation(&mut self, operation: &OperationDefinition) -> DiagnosticsResult<()> {
        // Initialize arguments state for @relay(mask: false),
        self.current_reachable_arguments = Default::default();
        validate!(
            self.default_validate_operation(operation),
            should_generate_hack_preloader(operation).map(|_| ()),
            if self.current_reachable_arguments.is_empty() {
                Ok(())
            } else {
                let mut map: HashMap<VariableName, _> = Default::default();
                for variable in &operation.variable_definitions {
                    map.insert(variable.name.item, ArgumentDefinition::Global(variable));
                }
                self.validate_reachable_arguments(&mut map)
            }
        )
    }

    fn validate_fragment_spread(&mut self, spread: &FragmentSpread) -> DiagnosticsResult<()> {
        validate!(
            if let Some(directive) = find_relay_directive(&spread.directives) {
                let mask_argument = directive.arguments.named(*MASK_ARG_NAME);
                if let Some(arg) = mask_argument {
                    match arg.value.item {
                        Value::Constant(ConstantValue::Boolean(val)) => {
                            if val {
                                Ok(())
                            } else {
                                self.validate_unmask_fragment_spread(spread)
                            }
                        }
                        Value::Constant(ConstantValue::Null()) => Ok(()),
                        _ => Err(vec![
                            Diagnostic::error(
                                ValidationMessage::InvalidRelayDirectiveArg(arg.name.item),
                                spread.fragment.location,
                            )
                            .annotate("related location", arg.value.location),
                        ]),
                    }
                } else {
                    Ok(())
                }
            } else {
                Ok(())
            },
            self.default_validate_fragment_spread(spread)
        )
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::DiagnosticsResult;
use common::Location;
use common::NamedItem;
use common::WithLocation;
use errors::validate;
use graphql_ir::Argument;
use graphql_ir::Directive;
use graphql_ir::Field;
use graphql_ir::FragmentDefinition;
use graphql_ir::LinkedField;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::ScalarField;
use graphql_ir::ValidationMessage;
use graphql_ir::Validator;
use graphql_ir::FIXME_FAT_INTERFACE;
use intern::string_key::StringKey;
use schema::ArgumentDefinitions;
use schema::Schema;

pub fn validate_required_arguments(program: &Program) -> DiagnosticsResult<()> {
    let mut validator = ValidateRequiredArguments::new(program);
    validator.validate_program(program)
}

struct ValidateRequiredArguments<'program> {
    program: &'program Program,
    root_name_with_location: Option<WithLocation<StringKey>>,
}

impl<'program> ValidateRequiredArguments<'program> {
    fn new(program: &'program Program) -> Self {
        Self {
            program,
            root_name_with_location: None,
        }
    }
}

impl Validator for ValidateRequiredArguments<'_> {
    const NAME: &'static str = "ValidateRequiredArguments";
    const VALIDATE_ARGUMENTS: bool = false;
    const VALIDATE_DIRECTIVES: bool = true;

    fn validate_operation(&mut self, operation: &OperationDefinition) -> DiagnosticsResult<()> {
        self.root_name_with_location = Some(WithLocation::new(
            operation.name.location,
            operation.name.item.0,
        ));
        self.default_validate_operation(operation)
    }

    fn validate_fragment(&mut self, fragment: &FragmentDefinition) -> DiagnosticsResult<()> {
        self.root_name_with_location = Some(fragment.name.map(|x| x.0));
        self.default_validate_fragment(fragment)
    }

    fn validate_scalar_field(&mut self, field: &ScalarField) -> DiagnosticsResult<()> {
        let definition = self.program.schema.field(field.definition.item);
        validate!(
            if field.directives.named(*FIXME_FAT_INTERFACE).is_some() {
                Ok(())
            } else {
                self.validate_required_arguments(
                    &definition.arguments,
                    &field.arguments,
                    field.alias_or_name(&self.program.schema),
                    field.alias_or_name_location(),
                    self.root_name_with_location.unwrap(),
                )
            },
            self.validate_directives(&field.directives)
        )
    }

    fn validate_linked_field(&mut self, field: &LinkedField) -> DiagnosticsResult<()> {
        let definition = self.program.schema.field(field.definition.item);
        validate!(
            if field.directives.named(*FIXME_FAT_INTERFACE).is_some() {
                Ok(())
            } else {
                self.validate_required_arguments(
                    &definition.arguments,
                    &field.arguments,
                    field.alias_or_name(&self.program.schema),
                    field.alias_or_name_location(),
                    self.root_name_with_location.unwrap(),
                )
            },
            self.default_validate_linked_field(field)
        )
    }

    fn validate_directive(&mut self, directive: &Directive) -> DiagnosticsResult<()> {
        let definition = self.program.schema.get_directive(directive.name.item);
        if let Some(definition) = definition {
            self.validate_required_arguments(
                &definition.arguments,
                &directive.arguments,
                directive.name.item.0,
                directive.name.location,
                self.root_name_with_location.unwrap(),
            )
        } else {
            Ok(())
        }
    }
}

impl ValidateRequiredArguments<'_> {
    fn validate_required_arguments(
        &self,
        argument_definitions: &ArgumentDefinitions,
        arguments: &[Argument],
        node_name: StringKey,
        node_location: Location,
        root_name_with_location: WithLocation<StringKey>,
    ) -> DiagnosticsResult<()> {
        if !argument_definitions.is_empty() {
            for def in argument_definitions.iter() {
                if def.type_.is_non_null()
                    && def.default_value.is_none()
                    && !arguments
                        .iter()
                        .map(|arg| arg.name.item)
                        .any(|x| x == def.name)
                {
                    return Err(vec![
                        Diagnostic::error(
                            ValidationMessage::MissingRequiredArgument {
                                argument_name: def.name,
                                node_name,
                                root_name: root_name_with_location.item,
                                type_string: self.program.schema.get_type_string(&def.type_),
                            },
                            node_location,
                        )
                        .annotate("Root definition:", root_name_with_location.location),
                    ]);
                }
            }
        }
        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::DiagnosticsResult;
use common::NamedItem;
use docblock_shared::FRAGMENT_KEY_ARGUMENT_NAME;
use docblock_shared::RELAY_RESOLVER_DIRECTIVE_NAME;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentDefinitionName;
use graphql_ir::FragmentDefinitionNameSet;
use graphql_ir::FragmentSpread;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::Validator;
use graphql_ir::Variable;
use schema::SDLSchema;
use schema::Schema;

use crate::relay_resolvers::get_argument_value;
use crate::ValidationMessage;

pub fn validate_resolver_fragments(program: &Program) -> DiagnosticsResult<()> {
    ValidateResolverFragments::new(&program.schema).validate_program(program)
}

struct ValidateResolverFragments {
    resolver_fragments: FragmentDefinitionNameSet,
    current_fragment: Option<FragmentDefinition>,
}

impl ValidateResolverFragments {
    fn new(schema: &SDLSchema) -> Self {
        let validator = Self {
            current_fragment: None,
            resolver_fragments: schema
                .fields()
                .filter_map(|field| {
                    if !field.is_extension {
                        return None;
                    }

                    field
                        .directives
                        .named(*RELAY_RESOLVER_DIRECTIVE_NAME)
                        .and_then(|directive| {
                            let arguments = &directive.arguments;
                            get_argument_value(
                                arguments,
                                *FRAGMENT_KEY_ARGUMENT_NAME,
                                field.name.location,
                            )
                            .ok()
                            .map(FragmentDefinitionName)
                        })
                })
                .collect::<FragmentDefinitionNameSet>(),
        };

        validator
    }
}

impl Validator for ValidateResolverFragments {
    const NAME: &'static str = "ValidateResolverFragments";
    const VALIDATE_ARGUMENTS: bool = true;
    const VALIDATE_DIRECTIVES: bool = true;

    fn validate_operation(&mut self, _operation: &OperationDefinition) -> DiagnosticsResult<()> {
        Ok(())
    }

    fn validate_fragment(&mut self, fragment: &FragmentDefinition) -> DiagnosticsResult<()> {
        if self.resolver_fragments.contains(&fragment.name.item) {
            self.current_fragment = Some(fragment.clone());
            let result = self.default_validate_fragment(fragment);
            self.current_fragment = None;

            result
        } else {
            Ok(())
        }
    }

    fn validate_fragment_spread(&mut self, spread: &FragmentSpread) -> DiagnosticsResult<()> {
        Err(vec![Diagnostic::error(
            format!(
                "Using fragment spread `...{}` is not supported in the Relay resolvers fragments. The runtime API for reading the data of these fragments is not implemented, yet.",
                spread.fragment.item
            ),
            spread.fragment.location,
        )])
    }

    fn validate_variable(&mut self, variable: &Variable) -> DiagnosticsResult<()> {
        let current_fragment = self.current_fragment.as_ref().unwrap();
        if !current_fragment
            .variable_definitions
            .iter()
            .any(|var| var.name.item == variable.name.item)
        {
            return Err(vec![Diagnostic::error(
                ValidationMessage::UnsupportedGlobalVariablesInResolverFragment {
                    variable_name: variable.name.item,
                    fragment_name: current_fragment.name.item,
                },
                variable.name.location,
            )]);
        }

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::DiagnosticsResult;
use common::DirectiveName;
use common::Location;
use common::WithLocation;
use errors::validate;
use errors::validate_map;
use graphql_ir::Directive;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentDefinitionNameMap;
use graphql_ir::FragmentSpread;
use graphql_ir::LinkedField;
use graphql_ir::Program;
use graphql_ir::ScalarField;
use graphql_ir::ValidationMessage;
use graphql_ir::Validator;
use intern::string_key::Intern;
use intern::string_key::StringKeySet;
use schema::Schema;

pub fn validate_server_only_directives(program: &Program) -> DiagnosticsResult<()> {
    let mut validator = ServerOnlyDirectivesValidation::new(program);
    validator.validate_program(program)
}

#[derive(Default, Clone)]
struct FragmentState {
    client_invalid_directives: Vec<WithLocation<DirectiveName>>,
    is_client_only: bool,
}

struct ServerOnlyDirectivesValidation<'s> {
    program: &'s Program,
    current_root_client_selection: Option<Location>,
    current_client_invalid_directives: Vec<WithLocation<DirectiveName>>,
    // For keeping track of if the current fragment only contains client selections,
    // if so, a server directive is invalid on it
    is_current_fragment_client_only: bool,
    // For storing the above data
    fragment_cache: FragmentDefinitionNameMap<FragmentState>,
    client_invalid_directive_names: StringKeySet,
}

// Validate that @defer, @stream, @stream_connection are not used inside client fields
impl<'s> ServerOnlyDirectivesValidation<'s> {
    fn new(program: &'s Program) -> Self {
        Self {
            program,
            current_root_client_selection: None,
            is_current_fragment_client_only: true,
            fragment_cache: Default::default(),
            current_client_invalid_directives: Default::default(),
            client_invalid_directive_names: StringKeySet::from_iter(vec![
                "stream".intern(),
                "stream_connection".intern(),
                "defer".intern(),
            ]),
        }
    }

    fn validate_visited_fragment(&self, state: &FragmentState) -> DiagnosticsResult<()> {
        if let Some(location) = self.current_root_client_selection {
            return Err(state
                .client_invalid_directives
                .iter()
                .map(|name| {
                    Diagnostic::error(
                        ValidationMessage::InvalidServerOnlyDirectiveInClientFields(name.item),
                        name.location,
                    )
                    .annotate("related location", location)
                })
                .collect());
        }
        Ok(())
    }

    // Traverse into a fragment from the program or a fragment spread
    #[allow(clippy::map_entry)]
    fn validate_fragment_impl(
        &mut self,
        fragment: &FragmentDefinition,
    ) -> Result<&FragmentState, Vec<Diagnostic>> {
        if self.fragment_cache.contains_key(&fragment.name.item) {
            // The fragment is already visited, check if we are in a client selection
            // and the fragment contains server selections.
            let state = self.fragment_cache.get(&fragment.name.item).unwrap();
            self.validate_visited_fragment(state)?;
            Ok(state)
        } else {
            // Avoid cycle
            self.fragment_cache
                .insert(fragment.name.item, Default::default());
            let parent_root_client_selection = self.current_root_client_selection;
            let parent_client_invalid_directives: Vec<WithLocation<DirectiveName>> =
                self.current_client_invalid_directives.clone();
            let parent_is_current_fragment_client_only = self.is_current_fragment_client_only;
            // Keep traversing the fragment with the current client selection root or the current client fragment root
            if self.current_root_client_selection.is_none()
                && self
                    .program
                    .schema
                    .is_extension_type(fragment.type_condition)
            {
                self.current_root_client_selection = Some(fragment.name.location)
            }
            self.current_client_invalid_directives = Default::default();
            self.is_current_fragment_client_only = true;

            // Traverse and cache the result
            self.default_validate_fragment(fragment)?;
            self.fragment_cache.insert(
                fragment.name.item,
                FragmentState {
                    client_invalid_directives: self.current_client_invalid_directives.clone(),
                    is_client_only: self.is_current_fragment_client_only,
                },
            );
            self.current_root_client_selection = parent_root_client_selection;
            self.current_client_invalid_directives = parent_client_invalid_directives;
            self.is_current_fragment_client_only = parent_is_current_fragment_client_only;
            Ok(self.fragment_cache.get(&fragment.name.item).unwrap())
        }
    }
}

impl<'s> Validator for ServerOnlyDirectivesValidation<'s> {
    const NAME: &'static str = "ServerOnlyDirectivesValidation";
    const VALIDATE_ARGUMENTS: bool = false;
    const VALIDATE_DIRECTIVES: bool = true;

    fn validate_program(&mut self, program: &Program) -> DiagnosticsResult<()> {
        validate!(
            validate_map(program.operations(), |operation| {
                self.current_client_invalid_directives = vec![];
                self.current_root_client_selection = None;
                self.is_current_fragment_client_only = true;
                self.validate_operation(operation)
            }),
            validate_map(program.fragments(), |fragment| {
                self.current_client_invalid_directives = vec![];
                self.current_root_client_selection = None;
                self.is_current_fragment_client_only = true;
                self.validate_fragment(fragment)
            })
        )
    }

    fn validate_fragment(&mut self, fragment: &FragmentDefinition) -> DiagnosticsResult<()> {
        self.validate_fragment_impl(fragment)?;
        Ok(())
    }

    fn validate_fragment_spread(&mut self, spread: &FragmentSpread) -> DiagnosticsResult<()> {
        let fragment = self.program.fragment(spread.fragment.item).unwrap();
        let state = self.validate_fragment_impl(fragment)?.clone();
        let FragmentState {
            is_client_only,
            client_invalid_directives,
        } = state;

        self.is_current_fragment_client_only =
            self.is_current_fragment_client_only && is_client_only;
        self.current_client_invalid_directives
            .extend(client_invalid_directives.into_iter());
        if is_client_only {
            let current_root_client_selection = self.current_root_client_selection;
            self.current_root_client_selection = Some(fragment.name.location);
            self.default_validate_fragment_spread(spread)?;
            self.current_root_client_selection = current_root_client_selection;
        }
        Ok(())
    }

    fn validate_linked_field(&mut self, field: &LinkedField) -> DiagnosticsResult<()> {
        let current_root_client_selection = self.current_root_client_selection;
        if self.current_root_client_selection.is_none()
            && self
                .program
                .schema
                .field(field.definition.item)
                .is_extension
        {
            self.current_root_client_selection = Some(field.definition.location)
        }
        if self.is_current_fragment_client_only
            && !self
                .program
                .schema
                .field(field.definition.item)
                .is_extension
        {
            self.is_current_fragment_client_only = false;
        }
        let result = self.default_validate_linked_field(field);
        self.current_root_client_selection = current_root_client_selection;
        result
    }

    fn validate_scalar_field(&mut self, field: &ScalarField) -> DiagnosticsResult<()> {
        if self.is_current_fragment_client_only
            && !self
                .program
                .schema
                .field(field.definition.item)
                .is_extension
        {
            self.is_current_fragment_client_only = false;
        }
        self.default_validate_scalar_field(field)
    }

    fn validate_directive(&mut self, directive: &Directive) -> DiagnosticsResult<()> {
        if self
            .client_invalid_directive_names
            .contains(&directive.name.item.0)
        {
            self.current_client_invalid_directives.push(directive.name);
            if let Some(location) = self.current_root_client_selection {
                Err(vec![
                    Diagnostic::error(
                        ValidationMessage::InvalidServerOnlyDirectiveInClientFields(
                            directive.name.item,
                        ),
                        directive.name.location,
                    )
                    .annotate("related location", location),
                ])
            } else {
                Ok(())
            }
        } else {
            Ok(())
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use ::intern::string_key::StringKey;
use common::ArgumentName;
use common::Diagnostic;
use common::DiagnosticsResult;
use common::DirectiveName;
use common::Named;
use common::NamedItem;
use fnv::FnvHashMap;
use fnv::FnvHashSet;
use graphql_ir::Argument as IRArgument;
use graphql_ir::Directive;
use graphql_ir::Program;
use graphql_ir::Validator;
use graphql_ir::Value;
use intern::intern;
use lazy_static::lazy_static;
use schema::Argument as SchemaArgument;
use schema::ArgumentDefinitions;
use schema::Schema;

use crate::ValidationMessage;

lazy_static! {
    static ref STATIC_ARG: DirectiveName = DirectiveName(intern!("static"));
}

pub fn validate_static_args(program: &Program) -> DiagnosticsResult<()> {
    StaticArgValidator::new(program).validate_program(program)
}

type FieldName = StringKey;
type StaticArguments = FnvHashSet<ArgumentName>;
type StaticArgCache = FnvHashMap<FieldName, StaticArguments>;

struct StaticArgValidator<'a> {
    program: &'a Program,
    field_to_static_args: StaticArgCache,
}

impl<'a> StaticArgValidator<'a> {
    fn new(program: &'a Program) -> Self {
        Self {
            program,
            field_to_static_args: StaticArgCache::default(),
        }
    }
}

impl<'a> Validator for StaticArgValidator<'a> {
    const NAME: &'static str = "StaticArgValidator";
    // Eliding default argument checks as we're overriding specific argument checks for certain types
    const VALIDATE_ARGUMENTS: bool = false;
    const VALIDATE_DIRECTIVES: bool = true;

    fn validate_directive(&mut self, directive: &Directive) -> DiagnosticsResult<()> {
        let validation_errors = self
            .program
            .schema
            .get_directive(directive.name.item)
            .map(|schema_directive| {
                validate_all_static_args(
                    &mut self.field_to_static_args,
                    schema_directive.name().0,
                    &schema_directive.arguments,
                    &directive.arguments,
                )
            })
            .unwrap_or_default();

        if validation_errors.is_empty() {
            Ok(())
        } else {
            Err(validation_errors)
        }
    }
}

fn validate_all_static_args<'a, 'b>(
    field_to_static_args: &'b mut StaticArgCache,
    field_name: StringKey,
    schema_arguments: &'a ArgumentDefinitions,
    ir_arguments: &'a [IRArgument],
) -> Vec<Diagnostic> {
    let static_args = field_to_static_args
        .entry(field_name)
        .or_insert_with(|| find_static_argument_names(schema_arguments));

    ir_arguments
        .iter()
        .filter_map(|arg| {
            if static_args.contains(&arg.name.item) && !is_constant_value(&arg.value.item) {
                Some(Diagnostic::error(
                    ValidationMessage::InvalidStaticArgument {
                        field_name,
                        argument_name: arg.name(),
                    },
                    arg.value.location,
                ))
            } else {
                None
            }
        })
        .collect()
}

fn find_static_argument_names(schema_arguments: &ArgumentDefinitions) -> FnvHashSet<ArgumentName> {
    schema_arguments
        .iter()
        .filter(|a| has_static_directive(a))
        .map(|a| a.name)
        .collect()
}

fn has_static_directive(argument: &SchemaArgument) -> bool {
    argument.directives.named(*STATIC_ARG).is_some()
}

fn is_constant_value(value: &Value) -> bool {
    matches!(value, Value::Constant(_))
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::collections::HashSet;

use common::Diagnostic;
use common::DiagnosticsResult;
use common::NamedItem;
use graphql_ir::FragmentDefinition;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::ValidationMessage;
use graphql_ir::Validator;
use graphql_ir::Variable;
use graphql_ir::VariableName;
use graphql_ir::UNUSED_LOCAL_VARIABLE_DEPRECATED;

/// Validates that there are no unused fragment variables on fragments.
///
/// Fragment variables are allowed (actually required) to be unused when
/// marked as `unusedLocalVariable_DEPRECATED`.
pub fn validate_unused_fragment_variables(program: &Program) -> DiagnosticsResult<()> {
    ValidateUnusedFragmentVariables::default().validate_program(program)
}

#[derive(Default)]
pub struct ValidateUnusedFragmentVariables {
    used_variables: HashSet<VariableName>,
}

impl Validator for ValidateUnusedFragmentVariables {
    const NAME: &'static str = "ValidateUnusedFragmentVariables";
    const VALIDATE_ARGUMENTS: bool = true;
    const VALIDATE_DIRECTIVES: bool = true;

    fn validate_operation(&mut self, _: &OperationDefinition) -> DiagnosticsResult<()> {
        Ok(())
    }

    fn validate_fragment(&mut self, fragment: &FragmentDefinition) -> DiagnosticsResult<()> {
        self.used_variables.clear();

        self.validate_selections(&fragment.selections)?;

        let diagnostics: Vec<_> = fragment
            .variable_definitions
            .iter()
            .filter_map(|variable_definition| {
                let actually_unused = !self.used_variables.contains(&variable_definition.name.item);
                let expect_unused_directive = variable_definition
                    .directives
                    .named(*UNUSED_LOCAL_VARIABLE_DEPRECATED);
                if expect_unused_directive.is_some() == actually_unused {
                    None
                } else if let Some(expect_unused_directive) = expect_unused_directive {
                    Some(Diagnostic::error(
                        ValidationMessage::UselessUnusedFragmentVariableAnnotation {
                            fragment_name: fragment.name.item,
                            variable_name: variable_definition.name.item,
                        },
                        expect_unused_directive.name.location,
                    ))
                } else {
                    Some(Diagnostic::error(
                        ValidationMessage::UnusedFragmentVariable {
                            fragment_name: fragment.name.item,
                            variable_name: variable_definition.name.item,
                        },
                        variable_definition.name.location,
                    ))
                }
            })
            .collect();
        if !diagnostics.is_empty() {
            return Err(diagnostics);
        }
        Ok(())
    }

    fn validate_variable(&mut self, value: &Variable) -> DiagnosticsResult<()> {
        self.used_variables.insert(value.name.item);
        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::DiagnosticsResult;
use common::DirectiveName;
use common::NamedItem;
use graphql_ir::FragmentDefinition;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::ValidationMessage;
use graphql_ir::Validator;
use intern::string_key::Intern;

use crate::root_variables::InferVariablesVisitor;

pub fn validate_unused_variables(program: &Program) -> DiagnosticsResult<()> {
    ValidateUnusedVariables::new(program).validate_program(program)
}

pub struct ValidateUnusedVariables<'program> {
    visitor: InferVariablesVisitor<'program>,
    ignore_directive_name: DirectiveName,
}

impl<'program> ValidateUnusedVariables<'program> {
    fn new(program: &'program Program) -> Self {
        Self {
            visitor: InferVariablesVisitor::new(program),
            ignore_directive_name: DirectiveName(
                "DEPRECATED__relay_ignore_unused_variables_error".intern(),
            ),
        }
    }
}

/// Validates that there are no unused variables in the operation.
/// former `graphql-js` NoUnusedVariablesRule
impl Validator for ValidateUnusedVariables<'_> {
    const NAME: &'static str = "ValidateUnusedVariables";
    const VALIDATE_ARGUMENTS: bool = false;
    const VALIDATE_DIRECTIVES: bool = false;

    fn validate_operation(&mut self, operation: &OperationDefinition) -> DiagnosticsResult<()> {
        let (variables, _) = self.visitor.infer_operation_variables(operation);
        let unused_variables: Vec<_> = operation
            .variable_definitions
            .iter()
            .filter(|var| !variables.contains_key(&var.name.item))
            .collect();

        let ignore_directive = operation.directives.named(self.ignore_directive_name);
        if !unused_variables.is_empty() && ignore_directive.is_none() {
            return Err(unused_variables
                .into_iter()
                .map(|unused_variable| {
                    Diagnostic::error(
                        ValidationMessage::UnusedVariable {
                            operation_name: operation.name.item.0,
                            variable_name: unused_variable.name.item,
                        },
                        unused_variable.name.location,
                    )
                })
                .collect());
        }
        if unused_variables.is_empty() {
            if let Some(directive) = ignore_directive {
                return Err(vec![Diagnostic::error(
                    ValidationMessage::UnusedIgnoreUnusedVariablesDirective {
                        operation_name: operation.name.item.0,
                    },
                    directive.name.location,
                )]);
            }
        }
        Ok(())
    }

    fn validate_fragment(&mut self, _: &FragmentDefinition) -> DiagnosticsResult<()> {
        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::path::Path;
use std::path::PathBuf;

/// Extract the module name from a path. This is the required prefix for
/// query, fragment, subscription and mutation names.
pub fn extract_module_name(path: &str) -> Option<String> {
    let path = PathBuf::from(path);
    let final_segment = get_final_non_index_js_segment(&path)?;
    let mut iter = final_segment.split('.');
    let mut first_segment = to_camel_case(iter.next()?.to_string());
    let mobile_platform = iter.find_map(|item| {
        if item == "ios" {
            return Some("Ios");
        } else if item == "android" {
            return Some("Android");
        }
        None
    });
    if let Some(mobile_platform) = mobile_platform {
        first_segment.push_str(mobile_platform);
    }
    Some(first_segment)
}

fn get_final_non_index_js_segment(path: &Path) -> Option<&str> {
    let file_stem = path.file_stem()?.to_str();
    if file_stem.map_or(false, |f| f == "index" || f.starts_with("index.")) {
        let mut iter = path.iter();
        iter.next_back()?;
        iter.next_back().and_then(|os_str| os_str.to_str())
    } else {
        file_stem
    }
}

/// Converts a `String` to a camel case `String`
fn to_camel_case(non_camelized_string: String) -> String {
    // Emulate the following regex, from the Javascript implementation:
    // moduleName = moduleName.replace(/[^a-zA-Z0-9]+(\w?)/g, (match, next) =>
    //     next.toUpperCase(),
    // );

    let mut camelized_string = String::with_capacity(non_camelized_string.len());
    let mut last_character_was_not_alphanumeric = false;
    for ch in non_camelized_string.chars() {
        if !ch.is_alphanumeric() {
            last_character_was_not_alphanumeric = true;
        } else if last_character_was_not_alphanumeric {
            camelized_string.push(ch.to_ascii_uppercase());
            last_character_was_not_alphanumeric = false;
        } else {
            camelized_string.push(ch);
            last_character_was_not_alphanumeric = false;
        }
    }
    camelized_string
}

#[cfg(test)]
mod tests {
    use super::*;

    #[cfg(not(windows))]
    #[test]
    fn extract_module_names_test() {
        assert_eq!(
            extract_module_name("/path/Button.ios.js"),
            Some("ButtonIos".to_string())
        );
        assert_eq!(
            extract_module_name("/path/Button.android.js"),
            Some("ButtonAndroid".to_string())
        );
        assert_eq!(
            extract_module_name("/path/Button.hooks.android.js"),
            Some("ButtonAndroid".to_string())
        );
        assert_eq!(
            extract_module_name("/path/Button.js"),
            Some("Button".to_string())
        );
        assert_eq!(
            extract_module_name("/path/Button.react.js"),
            Some("Button".to_string())
        );
        assert_eq!(
            extract_module_name("/path/Button.react-test.js"),
            Some("Button".to_string())
        );
        assert_eq!(
            extract_module_name("/path/Button.react-test.jsx"),
            Some("Button".to_string())
        );
        assert_eq!(
            extract_module_name("/path/Button.react.example.jsx"),
            Some("Button".to_string())
        );
        assert_eq!(
            extract_module_name("/path/Button.react-snapshot-test.js"),
            Some("Button".to_string())
        );
        assert_eq!(
            extract_module_name("/path/Button.kit.js"),
            Some("Button".to_string())
        );
        assert_eq!(
            extract_module_name("/path/Button.hooks.js"),
            Some("Button".to_string())
        );
        assert_eq!(
            extract_module_name("/path/Button.brands.react.js"),
            Some("Button".to_string())
        );
        assert_eq!(
            extract_module_name("/path/Button.my.custom.suffix.js"),
            Some("Button".to_string())
        );
        assert_eq!(
            extract_module_name("/path/Slider.ios.js"),
            Some("SliderIos".to_string())
        );
        assert_eq!(
            extract_module_name("/path/TypeScript.ts"),
            Some("TypeScript".to_string())
        );
        assert_eq!(
            extract_module_name("/path/TypeScript.tsx"),
            Some("TypeScript".to_string())
        );
        assert_eq!(
            extract_module_name("/path/TypeScript/index.ts"),
            Some("TypeScript".to_string())
        );
        assert_eq!(
            extract_module_name("/path/TypeScript/index.tsx"),
            Some("TypeScript".to_string())
        );
        assert_eq!(
            extract_module_name("/path/button/index.js"),
            Some("button".to_string())
        );
        assert_eq!(
            extract_module_name("/path/button/index.react.js"),
            Some("button".to_string())
        );
        assert_eq!(
            extract_module_name("/path/button/index.jsx"),
            Some("button".to_string())
        );
        assert_eq!(
            extract_module_name("/path/foo-bar/index.js"),
            Some("fooBar".to_string())
        );
        assert_eq!(
            extract_module_name("/path/foo-bar-baz.js"),
            Some("fooBarBaz".to_string())
        );
        assert_eq!(
            extract_module_name("/path/non-numeric-end-.js"),
            Some("nonNumericEnd".to_string())
        );
        assert_eq!(
            extract_module_name("/path/__tests__/index.test.js"),
            Some("Tests".to_string())
        );
        assert_eq!(
            extract_module_name("/path/button/indexButton.js"),
            Some("indexButton".to_string())
        );
    }

    #[cfg(windows)]
    #[test]
    fn extract_module_names_test() {
        assert_eq!(
            extract_module_name("C:\\path\\Button.ios.js"),
            Some("ButtonIos".to_string())
        );
        assert_eq!(
            extract_module_name("\\path\\Button.android.js"),
            Some("ButtonAndroid".to_string())
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

mod extract_module_name;

use common::Diagnostic;
use common::DiagnosticsResult;
use graphql_ir::FragmentDefinition;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::Validator;
use graphql_syntax::OperationKind;
use thiserror::Error;

pub fn validate_module_names(program: &Program) -> DiagnosticsResult<()> {
    (ValidateModuleNames {}).validate_program(program)
}

pub use extract_module_name::extract_module_name;

pub struct ValidateModuleNames {}

impl Validator for ValidateModuleNames {
    const NAME: &'static str = "ValidateModuleNames";
    const VALIDATE_ARGUMENTS: bool = false;
    const VALIDATE_DIRECTIVES: bool = true;

    fn validate_operation(&mut self, operation: &OperationDefinition) -> DiagnosticsResult<()> {
        let operation_name = operation.name.item.0.to_string();
        let path = operation.name.location.source_location().path();
        let module_name = extract_module_name(path).expect("Unable to extract module name.");
        let (operation_type_suffix, pluralized_string) = match operation.kind {
            OperationKind::Query => ("Query", "Queries"),
            OperationKind::Mutation => ("Mutation", "Mutations"),
            OperationKind::Subscription => ("Subscription", "Subscriptions"),
        };

        let operation_name_ending_is_valid = operation_name.ends_with("Query")
            || operation_name.ends_with("Mutation")
            || operation_name.ends_with("Subscription");

        if !operation_name.starts_with(&module_name) || !operation_name_ending_is_valid
        // TODO: T71484519 re-enable this line when queries are correctly named in www
        // || !operation_name.ends_with(operation_type_suffix)
        {
            return Err(vec![Diagnostic::error(
                ValidationMessage::InvalidOperationName {
                    pluralized_string: pluralized_string.to_string(),
                    operation_type_suffix: operation_type_suffix.to_string(),
                    module_name,
                    operation_name,
                },
                operation.name.location,
            )]);
        }

        Ok(())
    }

    fn validate_fragment(&mut self, fragment: &FragmentDefinition) -> DiagnosticsResult<()> {
        let fragment_name = fragment.name.item.to_string();
        let path = fragment.name.location.source_location().path();
        let module_name =
            extract_module_name::extract_module_name(path).expect("Unable to extract module name.");

        if !fragment_name.starts_with(&module_name) {
            return Err(vec![Diagnostic::error(
                ValidationMessage::InvalidFragmentName {
                    module_name,
                    fragment_name,
                },
                fragment.name.location,
            )]);
        }
        Ok(())
    }
}

#[derive(Debug, Error)]
pub enum ValidationMessage {
    #[error(
        "{pluralized_string} in graphql tags must start with the module name ('{module_name}') and end with '{operation_type_suffix}'. Got '{operation_name}' instead."
    )]
    InvalidOperationName {
        pluralized_string: String,
        module_name: String,
        operation_type_suffix: String,
        operation_name: String,
    },

    #[error(
        "Fragments in graphql tags must start with the module name ('{module_name}'). Got '{fragment_name}' instead."
    )]
    InvalidFragmentName {
        module_name: String,
        fragment_name: String,
    },
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<31a9fd31997eea7c6c3cf9a74ddaf459>>
 */

mod apply_fragment_arguments;

use apply_fragment_arguments::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn deletes_empty_fragments() {
    let input = include_str!("apply_fragment_arguments/fixtures/deletes-empty-fragments.graphql");
    let expected = include_str!("apply_fragment_arguments/fixtures/deletes-empty-fragments.expected");
    test_fixture(transform_fixture, "deletes-empty-fragments.graphql", "apply_fragment_arguments/fixtures/deletes-empty-fragments.expected", input, expected);
}

#[test]
fn deletes_unreferenced_fragments() {
    let input = include_str!("apply_fragment_arguments/fixtures/deletes-unreferenced-fragments.graphql");
    let expected = include_str!("apply_fragment_arguments/fixtures/deletes-unreferenced-fragments.expected");
    test_fixture(transform_fixture, "deletes-unreferenced-fragments.graphql", "apply_fragment_arguments/fixtures/deletes-unreferenced-fragments.expected", input, expected);
}

#[test]
fn fragment_include_with_provided_argument() {
    let input = include_str!("apply_fragment_arguments/fixtures/fragment-include-with-provided-argument.graphql");
    let expected = include_str!("apply_fragment_arguments/fixtures/fragment-include-with-provided-argument.expected");
    test_fixture(transform_fixture, "fragment-include-with-provided-argument.graphql", "apply_fragment_arguments/fixtures/fragment-include-with-provided-argument.expected", input, expected);
}

#[test]
fn fragment_with_float_argument() {
    let input = include_str!("apply_fragment_arguments/fixtures/fragment-with-float-argument.graphql");
    let expected = include_str!("apply_fragment_arguments/fixtures/fragment-with-float-argument.expected");
    test_fixture(transform_fixture, "fragment-with-float-argument.graphql", "apply_fragment_arguments/fixtures/fragment-with-float-argument.expected", input, expected);
}

#[test]
fn inline_fragment_with_provided_argument() {
    let input = include_str!("apply_fragment_arguments/fixtures/inline-fragment-with-provided-argument.graphql");
    let expected = include_str!("apply_fragment_arguments/fixtures/inline-fragment-with-provided-argument.expected");
    test_fixture(transform_fixture, "inline-fragment-with-provided-argument.graphql", "apply_fragment_arguments/fixtures/inline-fragment-with-provided-argument.expected", input, expected);
}

#[test]
fn inlines_fragment_arguments() {
    let input = include_str!("apply_fragment_arguments/fixtures/inlines-fragment-arguments.graphql");
    let expected = include_str!("apply_fragment_arguments/fixtures/inlines-fragment-arguments.expected");
    test_fixture(transform_fixture, "inlines-fragment-arguments.graphql", "apply_fragment_arguments/fixtures/inlines-fragment-arguments.expected", input, expected);
}

#[test]
fn inlines_literal_fragment_arguments() {
    let input = include_str!("apply_fragment_arguments/fixtures/inlines-literal-fragment-arguments.graphql");
    let expected = include_str!("apply_fragment_arguments/fixtures/inlines-literal-fragment-arguments.expected");
    test_fixture(transform_fixture, "inlines-literal-fragment-arguments.graphql", "apply_fragment_arguments/fixtures/inlines-literal-fragment-arguments.expected", input, expected);
}

#[test]
fn merges_identical_fragments() {
    let input = include_str!("apply_fragment_arguments/fixtures/merges-identical-fragments.graphql");
    let expected = include_str!("apply_fragment_arguments/fixtures/merges-identical-fragments.expected");
    test_fixture(transform_fixture, "merges-identical-fragments.graphql", "apply_fragment_arguments/fixtures/merges-identical-fragments.expected", input, expected);
}

#[test]
fn merges_identical_fragments_literal_arguments() {
    let input = include_str!("apply_fragment_arguments/fixtures/merges-identical-fragments-literal-arguments.graphql");
    let expected = include_str!("apply_fragment_arguments/fixtures/merges-identical-fragments-literal-arguments.expected");
    test_fixture(transform_fixture, "merges-identical-fragments-literal-arguments.graphql", "apply_fragment_arguments/fixtures/merges-identical-fragments-literal-arguments.expected", input, expected);
}

#[test]
fn multiple_queries_with_provided_argument() {
    let input = include_str!("apply_fragment_arguments/fixtures/multiple-queries-with-provided-argument.graphql");
    let expected = include_str!("apply_fragment_arguments/fixtures/multiple-queries-with-provided-argument.expected");
    test_fixture(transform_fixture, "multiple-queries-with-provided-argument.graphql", "apply_fragment_arguments/fixtures/multiple-queries-with-provided-argument.expected", input, expected);
}

#[test]
fn noncyclic_fragment_with_provided_argument() {
    let input = include_str!("apply_fragment_arguments/fixtures/noncyclic-fragment-with-provided-argument.graphql");
    let expected = include_str!("apply_fragment_arguments/fixtures/noncyclic-fragment-with-provided-argument.expected");
    test_fixture(transform_fixture, "noncyclic-fragment-with-provided-argument.graphql", "apply_fragment_arguments/fixtures/noncyclic-fragment-with-provided-argument.expected", input, expected);
}

#[test]
fn rejects_cyclic_fragments_invalid() {
    let input = include_str!("apply_fragment_arguments/fixtures/rejects-cyclic-fragments.invalid.graphql");
    let expected = include_str!("apply_fragment_arguments/fixtures/rejects-cyclic-fragments.invalid.expected");
    test_fixture(transform_fixture, "rejects-cyclic-fragments.invalid.graphql", "apply_fragment_arguments/fixtures/rejects-cyclic-fragments.invalid.expected", input, expected);
}

#[test]
fn uses_defaults_for_literal_null_arguments() {
    let input = include_str!("apply_fragment_arguments/fixtures/uses-defaults-for-literal-null-arguments.graphql");
    let expected = include_str!("apply_fragment_arguments/fixtures/uses-defaults-for-literal-null-arguments.expected");
    test_fixture(transform_fixture, "uses-defaults-for-literal-null-arguments.graphql", "apply_fragment_arguments/fixtures/uses-defaults-for-literal-null-arguments.expected", input, expected);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<b7d75b6fd50fd7b7dd28b7a17618e7b8>>
 */

mod assignable_directive;

use assignable_directive::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn assignable_fragment() {
    let input = include_str!("assignable_directive/fixtures/assignable-fragment.graphql");
    let expected = include_str!("assignable_directive/fixtures/assignable-fragment.expected");
    test_fixture(transform_fixture, "assignable-fragment.graphql", "assignable_directive/fixtures/assignable-fragment.expected", input, expected);
}

#[test]
fn assignable_fragment_aliased_typename_invalid() {
    let input = include_str!("assignable_directive/fixtures/assignable-fragment-aliased-typename.invalid.graphql");
    let expected = include_str!("assignable_directive/fixtures/assignable-fragment-aliased-typename.invalid.expected");
    test_fixture(transform_fixture, "assignable-fragment-aliased-typename.invalid.graphql", "assignable_directive/fixtures/assignable-fragment-aliased-typename.invalid.expected", input, expected);
}

#[test]
fn assignable_fragment_directive_on_typename_invalid() {
    let input = include_str!("assignable_directive/fixtures/assignable-fragment-directive-on-typename.invalid.graphql");
    let expected = include_str!("assignable_directive/fixtures/assignable-fragment-directive-on-typename.invalid.expected");
    test_fixture(transform_fixture, "assignable-fragment-directive-on-typename.invalid.graphql", "assignable_directive/fixtures/assignable-fragment-directive-on-typename.invalid.expected", input, expected);
}

#[test]
fn assignable_fragment_directive_other_directives_invalid() {
    let input = include_str!("assignable_directive/fixtures/assignable-fragment-directive-other-directives.invalid.graphql");
    let expected = include_str!("assignable_directive/fixtures/assignable-fragment-directive-other-directives.invalid.expected");
    test_fixture(transform_fixture, "assignable-fragment-directive-other-directives.invalid.graphql", "assignable_directive/fixtures/assignable-fragment-directive-other-directives.invalid.expected", input, expected);
}

#[test]
fn assignable_fragment_no_typename_invalid() {
    let input = include_str!("assignable_directive/fixtures/assignable-fragment-no-typename.invalid.graphql");
    let expected = include_str!("assignable_directive/fixtures/assignable-fragment-no-typename.invalid.expected");
    test_fixture(transform_fixture, "assignable-fragment-no-typename.invalid.graphql", "assignable_directive/fixtures/assignable-fragment-no-typename.invalid.expected", input, expected);
}

#[test]
fn assignable_fragment_other_fields_invalid() {
    let input = include_str!("assignable_directive/fixtures/assignable-fragment-other-fields.invalid.graphql");
    let expected = include_str!("assignable_directive/fixtures/assignable-fragment-other-fields.invalid.expected");
    test_fixture(transform_fixture, "assignable-fragment-other-fields.invalid.graphql", "assignable_directive/fixtures/assignable-fragment-other-fields.invalid.expected", input, expected);
}