<commit_before>/*
 * Copyright (c) Facebook, Inc. and its 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, DiagnosticsResult, Location, NamedItem, WithLocation};
use graphql_ir::{
    Condition, FragmentSpread, InlineFragment, LinkedField, OperationDefinition, Program,
    ScalarField, Selection, Transformed, Transformer, ValidationMessage,
};
use intern::string_key::{Intern, StringKey};
use lazy_static::lazy_static;
use schema::{FieldID, Schema};
use std::sync::Arc;

lazy_static! {
    static ref ASSIGNABLE_DIRECTIVE: StringKey = "assignable".intern();
    static ref UPDATABLE_DIRECTIVE: StringKey = "updatable".intern();
}

pub fn transform_assignable_fragment_spreads(program: &Program) -> DiagnosticsResult<Program> {
    let mut transform = AssignableFragmentSpread {
        program,
        errors: Default::default(),
        path: vec![],
    };

    let next_program = transform
        .transform_program(program)
        .replace_or_else(|| program.clone());

    if transform.errors.is_empty() {
        Ok(next_program)
    } else {
        Err(transform.errors)
    }
}

struct AssignableFragmentSpread<'s> {
    program: &'s Program,
    errors: Vec<Diagnostic>,
    path: Vec<PathSegment>,
}

impl<'s> AssignableFragmentSpread<'s> {
    /// 1. Validate that the assignable fragment does not have @skip/@defer, and
    /// is not within an inline fragment with directives, and is nested in a linked field
    /// 2. Mark the enclosing linked field as containing an assignable fragment spread.
    /// This later results in an __id (clientid_field) selection being added to the linked
    /// field.
    fn validate_nesting_and_mark_enclosing_linked_field(
        &mut self,
        fragment_spread: &FragmentSpread,
    ) {
        let mut directly_in_condition = None;
        let mut in_linked_field = false;
        let mut in_inline_fragment = false;

        for (index, item) in self.path.iter_mut().rev().enumerate() {
            match item {
                PathSegment::Condition(c) => {
                    // We can encounter a condition (before a linked field) in two ways:
                    // Directly on the assignable fragment spread: ... Assignable_foo @skip
                    // or
                    // As a directive on an inline fragment ... @skip { ...Assignable_foo }
                    if index == 0 {
                        directly_in_condition = Some(*c);
                    }
                }
                PathSegment::LinkedField {
                    encountered_assignable_fragment_spread,
                } => {
                    in_linked_field = true;
                    *encountered_assignable_fragment_spread = true;
                    break;
                }
                PathSegment::InlineFragment => {
                    in_inline_fragment = true;
                }
            }
        }

        if let Some(condition_directive_name) = directly_in_condition {
            self.errors.push(Diagnostic::error(
                ValidationMessage::AssignableFragmentSpreadNoOtherDirectives {
                    disallowed_directive_name: condition_directive_name.intern(),
                },
                fragment_spread.fragment.location,
            ));
        }
        if in_inline_fragment {
            self.errors.push(Diagnostic::error(
                ValidationMessage::AssignableFragmentSpreadNotWithinInlineFragment,
                fragment_spread.fragment.location,
            ));
        }
        if !in_linked_field {
            self.errors.push(Diagnostic::error(
                ValidationMessage::AssignableNoTopLevelFragmentSpreads,
                fragment_spread.fragment.location,
            ));
        }
    }
}

#[derive(Debug)]
enum PathSegment {
    Condition(&'static str),
    LinkedField {
        encountered_assignable_fragment_spread: bool,
    },
    InlineFragment,
}

impl<'s> Transformer for AssignableFragmentSpread<'s> {
    const NAME: &'static str = "RequiredDirectiveTransform";
    const VISIT_ARGUMENTS: bool = false;
    const VISIT_DIRECTIVES: bool = false;

    fn transform_operation(
        &mut self,
        operation: &OperationDefinition,
    ) -> Transformed<OperationDefinition> {
        if operation.directives.named(*UPDATABLE_DIRECTIVE).is_some() {
            Transformed::Keep<commit_msg>Update transform's NAME field to be accurate<commit_after>/*
 * Copyright (c) Facebook, Inc. and its 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, DiagnosticsResult, Location, NamedItem, WithLocation};
use graphql_ir::{
    Condition, FragmentSpread, InlineFragment, LinkedField, OperationDefinition, Program,
    ScalarField, Selection, Transformed, Transformer, ValidationMessage,
};
use intern::string_key::{Intern, StringKey};
use lazy_static::lazy_static;
use schema::{FieldID, Schema};
use std::sync::Arc;

lazy_static! {
    static ref ASSIGNABLE_DIRECTIVE: StringKey = "assignable".intern();
    static ref UPDATABLE_DIRECTIVE: StringKey = "updatable".intern();
}

pub fn transform_assignable_fragment_spreads(program: &Program) -> DiagnosticsResult<Program> {
    let mut transform = AssignableFragmentSpread {
        program,
        errors: Default::default(),
        path: vec![],
    };

    let next_program = transform
        .transform_program(program)
        .replace_or_else(|| program.clone());

    if transform.errors.is_empty() {
        Ok(next_program)
    } else {
        Err(transform.errors)
    }
}

struct AssignableFragmentSpread<'s> {
    program: &'s Program,
    errors: Vec<Diagnostic>,
    path: Vec<PathSegment>,
}

impl<'s> AssignableFragmentSpread<'s> {
    /// 1. Validate that the assignable fragment does not have @skip/@defer, and
    /// is not within an inline fragment with directives, and is nested in a linked field
    /// 2. Mark the enclosing linked field as containing an assignable fragment spread.
    /// This later results in an __id (clientid_field) selection being added to the linked
    /// field.
    fn validate_nesting_and_mark_enclosing_linked_field(
        &mut self,
        fragment_spread: &FragmentSpread,
    ) {
        let mut directly_in_condition = None;
        let mut in_linked_field = false;
        let mut in_inline_fragment = false;

        for (index, item) in self.path.iter_mut().rev().enumerate() {
            match item {
                PathSegment::Condition(c) => {
                    // We can encounter a condition (before a linked field) in two ways:
                    // Directly on the assignable fragment spread: ... Assignable_foo @skip
                    // or
                    // As a directive on an inline fragment ... @skip { ...Assignable_foo }
                    if index == 0 {
                        directly_in_condition = Some(*c);
                    }
                }
                PathSegment::LinkedField {
                    encountered_assignable_fragment_spread,
                } => {
                    in_linked_field = true;
                    *encountered_assignable_fragment_spread = true;
                    break;
                }
                PathSegment::InlineFragment => {
                    in_inline_fragment = true;
                }
            }
        }

        if let Some(condition_directive_name) = directly_in_condition {
            self.errors.push(Diagnostic::error(
                ValidationMessage::AssignableFragmentSpreadNoOtherDirectives {
                    disallowed_directive_name: condition_directive_name.intern(),
                },
                fragment_spread.fragment.location,
            ));
        }
        if in_inline_fragment {
            self.errors.push(Diagnostic::error(
                ValidationMessage::AssignableFragmentSpreadNotWithinInlineFragment,
                fragment_spread.fragment.location,
            ));
        }
        if !in_linked_field {
            self.errors.push(Diagnostic::error(
                ValidationMessage::AssignableNoTopLevelFragmentSpreads,
                fragment_spread.fragment.location,
            ));
        }
    }
}

#[derive(Debug)]
enum PathSegment {
    Condition(&'static str),
    LinkedField {
        encountered_assignable_fragment_spread: bool,
    },
    InlineFragment,
}

impl<'s> Transformer for AssignableFragmentSpread<'s> {
    const NAME: &'static str = "AssignableFragmentTransform";
    const VISIT_ARGUMENTS: bool = false;
    const VISIT_DIRECTIVES: bool = false;

    fn transform_operation(
        &mut self,
        operation: &OperationDefinition,
    ) -> Transformed<OperationDefinition> {
        if operation.directives.named(*UPDATABLE_DIRECTIVE).is_some() {
            Transformed::Keep
{'fraction_non_alphanumeric': 0.09069611780455154, 'fraction_numerical': 0.0006693440428380187, 'mean_word_length': 2.263560247542774, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5326649', 'n_tokens_mistral': 2414, 'n_tokens_neox': 2339, 'n_words': 573}