/*
 * 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 lsp_types::Position;
use lsp_types::Range;
use serde::Deserialize;
use serde::Serialize;

use crate::Span;

/// Represents GraphQL text extracted from a source file
/// The GraphQL text is potentially in some subrange of
/// the file, like a JS file.
/// Stores the text and some location information for
/// error reporting.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TextSource {
    pub text: String,
    pub line_index: usize,
    pub column_index: usize,
}

impl TextSource {
    pub fn new(text: impl Into<String>, line_index: usize, column_index: usize) -> Self {
        TextSource {
            text: text.into(),
            line_index,
            column_index,
        }
    }

    pub fn from_whole_document(text: impl Into<String>) -> Self {
        TextSource::new(text, 0, 0)
    }

    // Generate an LSP Range for this GraphQL source string. This provides the absolute
    // range of the entire GraphQL string within the source file.
    pub fn to_range(&self) -> Range {
        // Zero-indexed line offset in the document
        let mut line = self.line_index;
        // Zero-indexed character offset on the line
        let mut character = self.column_index;
        let mut chars = self.text.chars().peekable();
        let start_position = Position::new(line as u32, character as u32);
        while let Some(chr) = chars.next() {
            let is_newline = match chr {
                // Line terminators: https://www.ecma-international.org/ecma-262/#sec-line-terminators
                '\u{000A}' | '\u{000D}' | '\u{2028}' | '\u{2029}' => {
                    !matches!((chr, chars.peek()), ('\u{000D}', Some('\u{000D}')))
                }
                _ => false,
            };

            if is_newline {
                // New line, increment the line offset and reset the
                // character offset.
                line += 1;
                character = 0;
            }
            // Make sure to only increment the character offset if this
            // isn't a newline.
            if !is_newline {
                character += 1;
            }
        }
        let end_position = Position::new(line as u32, character as u32);
        Range::new(start_position, end_position)
    }

    pub fn to_span_range(&self, span: Span) -> lsp_types::Range {
        let start = span.start as usize;
        let end = span.end as usize;
        // Zero-indexed line offset in the document
        let mut line = self.line_index;
        // Zero-indexed character offset on the line
        let mut character = self.column_index;
        let mut start_position = lsp_types::Position::default();
        let mut end_position = lsp_types::Position::default();
        let mut chars = self.text.chars().enumerate().peekable();

        while let Some((index, chr)) = chars.next() {
            if index == start {
                start_position = lsp_types::Position::new(line as u32, character as u32);
            }
            if index == end {
                end_position = lsp_types::Position::new(line as u32, character as u32);
                break;
            }

            let is_newline = match chr {
                // Line terminators: https://www.ecma-international.org/ecma-262/#sec-line-terminators
                '\u{000A}' | '\u{000D}' | '\u{2028}' | '\u{2029}' => {
                    // <CLRF>
                    !matches!((chr, chars.peek()), ('\u{000D}', Some((_, '\u{000D}'))))
                }
                _ => false,
            };

            if is_newline {
                // New line, increment the line offset and reset the
                // character offset.
                line += 1;
                character = 0;
            } else {
                character += 1;
            }
        }
        lsp_types::Range::new(start_position, end_position)
    }
}

#[cfg(test)]
mod test {
    use super::TextSource;
    use crate::Span;

    #[test]
    fn to_range_test() {
        let span = Span::new(0, 5);
        let text_source = TextSource::new("source", 0, 0);
        let range = text_source.to_span_range(span);
        assert_eq!(range.start, lsp_types::Position::new(0, 0));
        assert_eq!(range.end, lsp_types::Position::new(0, 5));
    }

    #[test]
    fn to_range_multi_line_test() {
        // this range contains all characters of `fn foo ...`
        let span = Span::new(1, 23);
        let text_source = TextSource::new(
            r#"
fn foo() {
    error
}
        "#,
            0,
            0,
        );
        let range = text_source.to_span_range(span);
        assert_eq!(range.start, lsp_types::Position::new(1, 0));
        assert_eq!(range.end, lsp_types::Position::new(3, 1));
    }

    #[test]
    fn to_range_multi_line_test_2() {
        let span = Span::new(16, 21);
        let text_source = TextSource::new(
            r#"
fn foo() {
    error
}
        "#,
            0,
            0,
        );
        let range = text_source.to_span_range(span);
        assert_eq!(range.start, lsp_types::Position::new(2, 4));
        assert_eq!(range.end, lsp_types::Position::new(2, 9));
    }
}


/*
 * 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 graphql_ir::FragmentDefinitionName;
use graphql_ir::FragmentDefinitionNameMap;
use graphql_ir::FragmentDefinitionNameSet;
use graphql_ir::OperationDefinitionName;
use graphql_syntax::*;

use crate::ExecutableDefinitionNameMap;
use crate::ExecutableDefinitionNameSet;

pub struct ReachableAst {
    /// The definitions that need to be compiled, includes the project
    /// definitions plus the fragment definitions in the base definitions
    /// reachable from the project definitions.
    pub definitions: Vec<ExecutableDefinition>,
    /// The fragment names added from the base definitions.
    pub base_fragment_names: FragmentDefinitionNameSet,
}

/// Get all definitions that are reachable from project definitions
pub fn get_reachable_ast(
    project_definitions: Vec<ExecutableDefinition>,
    base_definitions: Vec<ExecutableDefinition>,
) -> ReachableAst {
    if base_definitions.is_empty() {
        return ReachableAst {
            definitions: project_definitions,
            base_fragment_names: Default::default(),
        };
    }

    let mut reachable_base_asts = Default::default();
    let mut base_definitions_map: FragmentDefinitionNameMap<ExecutableDefinition> =
        Default::default();

    // Preprocess all base fragment definitions
    // Skipping operations because project definitions can't reference base operations
    for base_definition in base_definitions {
        match &base_definition {
            ExecutableDefinition::Fragment(fragment) => {
                let name = FragmentDefinitionName(fragment.name.value);
                assert!(
                    base_definitions_map.insert(name, base_definition).is_none(),
                    "get_reachable_ast called on graph with duplicate definition of `{}`",
                    name
                )
            }
            ExecutableDefinition::Operation(_) => {}
        }
    }

    let mut result = project_definitions.clone();

    // Find references from project definitions -> base definitions
    for definition in project_definitions {
        let selections = match definition {
            ExecutableDefinition::Operation(definition) => definition.selections,
            ExecutableDefinition::Fragment(definition) => definition.selections,
        };
        visit_selections(
            &base_definitions_map,
            &mut reachable_base_asts,
            &selections,
            false,
        )
    }

    for key in &reachable_base_asts {
        let value = base_definitions_map.remove(key).unwrap();
        result.push(value);
    }

    ReachableAst {
        definitions: result,
        base_fragment_names: reachable_base_asts,
    }
}

/// Get fragment references of each definition
pub fn get_definition_references<'a>(
    definitions: impl IntoIterator<Item = &'a ExecutableDefinition>,
) -> ExecutableDefinitionNameMap<ExecutableDefinitionNameSet> {
    let mut result: ExecutableDefinitionNameMap<ExecutableDefinitionNameSet> = Default::default();
    for definition in definitions {
        let name = definition.name().expect("Expect a name on an operation");
        let name = match definition {
            ExecutableDefinition::Operation(_) => OperationDefinitionName(name).into(),
            ExecutableDefinition::Fragment(_) => FragmentDefinitionName(name).into(),
        };
        let mut selections: Vec<_> = match definition {
            ExecutableDefinition::Operation(definition) => &definition.selections.items,
            ExecutableDefinition::Fragment(definition) => &definition.selections.items,
        }
        .iter()
        .collect();
        let mut references: ExecutableDefinitionNameSet = Default::default();
        while let Some(selection) = selections.pop() {
            match selection {
                Selection::FragmentSpread(selection) => {
                    references.insert(FragmentDefinitionName(selection.name.value).into());
                }
                Selection::LinkedField(selection) => {
                    selections.extend(&selection.selections.items);
                }
                Selection::InlineFragment(selection) => {
                    selections.extend(&selection.selections.items);
                }
                Selection::ScalarField(_) => {}
            }
        }
        result.insert(name, references);
    }
    result
}

fn visit_selections(
    base_definitions_map: &FragmentDefinitionNameMap<ExecutableDefinition>,
    reachable_base_asts: &mut FragmentDefinitionNameSet,
    selections: &List<Selection>,
    is_base: bool,
) {
    for selection in &selections.items {
        match selection {
            Selection::FragmentSpread(selection) => {
                let fragment_name = FragmentDefinitionName(selection.name.value);
                if is_base || base_definitions_map.contains_key(&fragment_name) {
                    traverse_base_ast_definition(
                        base_definitions_map,
                        reachable_base_asts,
                        fragment_name,
                    )
                }
            }
            Selection::LinkedField(selection) => visit_selections(
                base_definitions_map,
                reachable_base_asts,
                &selection.selections,
                is_base,
            ),
            Selection::InlineFragment(selection) => visit_selections(
                base_definitions_map,
                reachable_base_asts,
                &selection.selections,
                is_base,
            ),
            Selection::ScalarField(_) => {}
        }
    }
}

fn traverse_base_ast_definition(
    base_definitions_map: &FragmentDefinitionNameMap<ExecutableDefinition>,
    reachable_base_asts: &mut FragmentDefinitionNameSet,
    key: FragmentDefinitionName,
) {
    if reachable_base_asts.contains(&key) {
        return;
    }
    if let Some(base_definition) = base_definitions_map.get(&key) {
        reachable_base_asts.insert(key);
        let selections = match base_definition {
            ExecutableDefinition::Operation(definition) => &definition.selections,
            ExecutableDefinition::Fragment(definition) => &definition.selections,
        };
        visit_selections(base_definitions_map, reachable_base_asts, selections, true)
    }
}


/*
 * 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::hash_map::Entry;
use std::collections::HashMap;
use std::fmt;

use graphql_ir::*;
use relay_transforms::get_resolver_fragment_dependency_name;
use rustc_hash::FxHashMap;
use rustc_hash::FxHashSet;
use schema::SDLSchema;
use schema::Schema;

pub type ExecutableDefinitionNameSet = FxHashSet<ExecutableDefinitionName>;
pub type ExecutableDefinitionNameMap<V> = FxHashMap<ExecutableDefinitionName, V>;
pub type ExecutableDefinitionNameVec = Vec<ExecutableDefinitionName>;

struct Node {
    ir: Option<ExecutableDefinition>,
    parents: ExecutableDefinitionNameVec,
    children: ExecutableDefinitionNameVec,
}

impl fmt::Debug for Node {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!(
            "parents: {:?} / children: {:?}",
            self.parents, self.children
        ))
    }
}

/// Find the set of executable definitions that are potentially impacted by the
/// set of changed documents declared in `changed_names`. This is achieved by
/// building a dependency graph where edges are either explicit fragment spreads,
/// or "implicit dependencies" such as those created by Relay Resolvers.
///
/// New implicit dependencies are detected by walking the changed documents,
/// whereas preexisting implicit dependencies must be passed in as
/// `implicit_dependencies`.
pub fn get_reachable_ir(
    definitions: Vec<ExecutableDefinition>,
    base_definition_names: ExecutableDefinitionNameSet,
    changed_names: ExecutableDefinitionNameSet,
    schema: &SDLSchema,
) -> Vec<ExecutableDefinition> {
    if changed_names.is_empty() {
        return vec![];
    }

    // For each executable definition, define a `Node` indicating its parents and children
    // Note: There are situations where a name in `changed_names` may not appear
    // in `definitions`, and thus would be missing from `dependency_graph`. This can arise
    // if you change a file which contains a fragment which is present in the
    // base project, but is not reachable from any of the project's own
    // queries/mutations.
    let dependency_graph = build_dependency_graph(schema, definitions);

    let mut visited = Default::default();
    let mut filtered_definitions = Default::default();

    for key in changed_names.into_iter() {
        if dependency_graph.contains_key(&key) {
            add_related_nodes(
                &mut visited,
                &mut filtered_definitions,
                &dependency_graph,
                &base_definition_names,
                key,
            );
        }
    }

    filtered_definitions
        .drain()
        .map(|(_, definition)| definition)
        .collect()
}

// Build a dependency graph of that nodes are "doubly linked"
fn build_dependency_graph(
    schema: &SDLSchema,
    definitions: Vec<ExecutableDefinition>,
) -> ExecutableDefinitionNameMap<Node> {
    let mut dependency_graph =
        HashMap::with_capacity_and_hasher(definitions.len(), Default::default());

    for definition in definitions.into_iter() {
        let name = match &definition {
            ExecutableDefinition::Operation(operation) => operation.name.item.into(),
            ExecutableDefinition::Fragment(fragment) => fragment.name.item.into(),
        };

        // Visit the selections of the IR to build it's `children`
        let mut children = vec![];
        let selections = match &definition {
            ExecutableDefinition::Operation(operation) => &operation.selections,
            ExecutableDefinition::Fragment(fragment) => &fragment.selections,
        };
        visit_selections(
            schema,
            &mut dependency_graph,
            selections,
            name,
            &mut children,
        );

        // Insert or update the representation of the IR in the dependency tree
        match dependency_graph.entry(name) {
            // Add a new node for current IR to the dependency tree
            Entry::Vacant(entry) => {
                entry.insert(Node {
                    ir: Some(definition),
                    parents: vec![],
                    children,
                });
            }

            // The node is already created when visiting selections of the IR, but it's `ir` and `children` haven't been set
            Entry::Occupied(mut entry) => {
                let node = entry.get_mut();
                if let Some(def) = &node.ir {
                    panic!(
                        "Duplicate definition: had {:?} and found another {:?}.",
                        def, node.ir
                    );
                }
                node.ir = Some(definition);
                node.children = children;
            }
        }
    }
    dependency_graph
}

fn update_dependency_graph(
    current_node: ExecutableDefinitionName,
    parent_name: ExecutableDefinitionName,
    dependency_graph: &mut ExecutableDefinitionNameMap<Node>,
    children: &mut ExecutableDefinitionNameVec,
) {
    match dependency_graph.get_mut(&current_node) {
        None => {
            dependency_graph.insert(
                current_node,
                Node {
                    ir: None,
                    parents: vec![parent_name],
                    children: vec![],
                },
            );
        }
        Some(node) => {
            node.parents.push(parent_name);
        }
    }
    children.push(current_node);
}

// Visit the selections of current IR, set the `children` for the node representing the IR,
// and the `parents` for nodes representing the children IR
fn visit_selections(
    schema: &SDLSchema,
    dependency_graph: &mut ExecutableDefinitionNameMap<Node>,
    selections: &[Selection],
    parent_name: ExecutableDefinitionName,
    children: &mut ExecutableDefinitionNameVec,
) {
    for selection in selections {
        match selection {
            Selection::FragmentSpread(node) => {
                let current_node = node.fragment.item.into();
                update_dependency_graph(current_node, parent_name, dependency_graph, children);
            }
            Selection::InlineFragment(node) => {
                visit_selections(
                    schema,
                    dependency_graph,
                    &node.selections,
                    parent_name,
                    children,
                );
            }
            Selection::LinkedField(linked_field) => {
                if let Some(fragment_name) = get_resolver_fragment_dependency_name(
                    schema.field(linked_field.definition.item),
                ) {
                    update_dependency_graph(
                        fragment_name.into(),
                        parent_name,
                        dependency_graph,
                        children,
                    );
                }
                visit_selections(
                    schema,
                    dependency_graph,
                    &linked_field.selections,
                    parent_name,
                    children,
                );
            }
            Selection::ScalarField(scalar_field) => {
                if let Some(fragment_name) = get_resolver_fragment_dependency_name(
                    schema.field(scalar_field.definition.item),
                ) {
                    update_dependency_graph(
                        fragment_name.into(),
                        parent_name,
                        dependency_graph,
                        children,
                    );
                }
            }
            Selection::Condition(node) => {
                visit_selections(
                    schema,
                    dependency_graph,
                    &node.selections,
                    parent_name,
                    children,
                );
            }
        }
    }
}

// From `key` of changed definition, recursively traverse up the dependency tree, and add all related nodes (ancestors
// of changed definitions which are not from base definitions, and all of their descendants) into the `result`
fn add_related_nodes(
    visited: &mut ExecutableDefinitionNameSet,
    result: &mut ExecutableDefinitionNameMap<ExecutableDefinition>,
    dependency_graph: &ExecutableDefinitionNameMap<Node>,
    base_definition_names: &ExecutableDefinitionNameSet,
    key: ExecutableDefinitionName,
) {
    if !visited.insert(key) {
        return;
    }

    let parents = match dependency_graph.get(&key) {
        None => {
            panic!("Fragment {:?} not found in IR.", key);
        }
        Some(node) => &node.parents,
    };
    if parents.is_empty() {
        if !base_definition_names.contains(&key) {
            add_descendants(result, dependency_graph, key);
        }
    } else {
        for parent in parents {
            add_related_nodes(
                visited,
                result,
                dependency_graph,
                base_definition_names,
                *parent,
            );
        }
    }
}

// Recursively add all descendants of current node into the `result`
fn add_descendants(
    result: &mut ExecutableDefinitionNameMap<ExecutableDefinition>,
    dependency_graph: &ExecutableDefinitionNameMap<Node>,
    key: ExecutableDefinitionName,
) {
    if result.contains_key(&key) {
        return;
    }
    match dependency_graph.get(&key) {
        Some(Node {
            ir: Some(def),
            children,
            ..
        }) => {
            result.insert(key, def.clone());
            for child in children {
                add_descendants(result, dependency_graph, *child);
            }
        }
        _ => {
            panic!("Fragment {:?} not found in IR.", key);
        }
    }
}


/*
 * 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.
 */

// Analyze IR dependencies and filter out the irrevelant IRs
#![deny(warnings)]
#![deny(rust_2018_idioms)]
#![deny(clippy::all)]

mod ast;
mod ir;

pub use ast::get_definition_references;
pub use ast::get_reachable_ast;
pub use ast::ReachableAst;
pub use ir::get_reachable_ir;
pub use ir::ExecutableDefinitionNameMap;
pub use ir::ExecutableDefinitionNameSet;
pub use ir::ExecutableDefinitionNameVec;


/*
 * 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<<3ecc95204640f547290f1ab25b144c25>>
 */

mod ast;

use ast::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn base_definitions() {
    let input = include_str!("ast/fixtures/base-definitions.graphql");
    let expected = include_str!("ast/fixtures/base-definitions.expected");
    test_fixture(transform_fixture, "base-definitions.graphql", "ast/fixtures/base-definitions.expected", input, expected);
}

#[test]
fn definitions_only() {
    let input = include_str!("ast/fixtures/definitions-only.graphql");
    let expected = include_str!("ast/fixtures/definitions-only.expected");
    test_fixture(transform_fixture, "definitions-only.graphql", "ast/fixtures/definitions-only.expected", input, expected);
}

#[test]
fn missing_fragments() {
    let input = include_str!("ast/fixtures/missing-fragments.graphql");
    let expected = include_str!("ast/fixtures/missing-fragments.expected");
    test_fixture(transform_fixture, "missing-fragments.graphql", "ast/fixtures/missing-fragments.expected", input, expected);
}

#[test]
fn multiple_base_definitions() {
    let input = include_str!("ast/fixtures/multiple-base-definitions.graphql");
    let expected = include_str!("ast/fixtures/multiple-base-definitions.expected");
    test_fixture(transform_fixture, "multiple-base-definitions.graphql", "ast/fixtures/multiple-base-definitions.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<<1370ea763208b340fd15717d45729c87>>
 */

mod ir;

use ir::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn base_definitions_change_fragment() {
    let input = include_str!("ir/fixtures/base-definitions-change-fragment.graphql");
    let expected = include_str!("ir/fixtures/base-definitions-change-fragment.expected");
    test_fixture(transform_fixture, "base-definitions-change-fragment.graphql", "ir/fixtures/base-definitions-change-fragment.expected", input, expected);
}

#[test]
fn base_definitions_change_fragment2() {
    let input = include_str!("ir/fixtures/base-definitions-change-fragment2.graphql");
    let expected = include_str!("ir/fixtures/base-definitions-change-fragment2.expected");
    test_fixture(transform_fixture, "base-definitions-change-fragment2.graphql", "ir/fixtures/base-definitions-change-fragment2.expected", input, expected);
}

#[test]
fn base_definitions_change_query() {
    let input = include_str!("ir/fixtures/base-definitions-change-query.graphql");
    let expected = include_str!("ir/fixtures/base-definitions-change-query.expected");
    test_fixture(transform_fixture, "base-definitions-change-query.graphql", "ir/fixtures/base-definitions-change-query.expected", input, expected);
}

#[test]
fn definitions_only_change_fragment() {
    let input = include_str!("ir/fixtures/definitions-only-change-fragment.graphql");
    let expected = include_str!("ir/fixtures/definitions-only-change-fragment.expected");
    test_fixture(transform_fixture, "definitions-only-change-fragment.graphql", "ir/fixtures/definitions-only-change-fragment.expected", input, expected);
}

#[test]
fn definitions_only_change_query() {
    let input = include_str!("ir/fixtures/definitions-only-change-query.graphql");
    let expected = include_str!("ir/fixtures/definitions-only-change-query.expected");
    test_fixture(transform_fixture, "definitions-only-change-query.graphql", "ir/fixtures/definitions-only-change-query.expected", input, expected);
}

#[test]
fn definitions_only_no_change() {
    let input = include_str!("ir/fixtures/definitions-only-no-change.graphql");
    let expected = include_str!("ir/fixtures/definitions-only-no-change.expected");
    test_fixture(transform_fixture, "definitions-only-no-change.graphql", "ir/fixtures/definitions-only-no-change.expected", input, expected);
}

#[test]
fn implicit_dependencies_parent_child() {
    let input = include_str!("ir/fixtures/implicit-dependencies-parent-child.graphql");
    let expected = include_str!("ir/fixtures/implicit-dependencies-parent-child.expected");
    test_fixture(transform_fixture, "implicit-dependencies-parent-child.graphql", "ir/fixtures/implicit-dependencies-parent-child.expected", input, expected);
}

#[test]
fn new_resolver_field() {
    let input = include_str!("ir/fixtures/new-resolver-field.graphql");
    let expected = include_str!("ir/fixtures/new-resolver-field.expected");
    test_fixture(transform_fixture, "new-resolver-field.graphql", "ir/fixtures/new-resolver-field.expected", input, expected);
}

#[test]
fn new_resolver_model_field() {
    let input = include_str!("ir/fixtures/new-resolver-model-field.graphql");
    let expected = include_str!("ir/fixtures/new-resolver-model-field.expected");
    test_fixture(transform_fixture, "new-resolver-model-field.graphql", "ir/fixtures/new-resolver-model-field.expected", input, expected);
}

#[test]
fn new_resolver_model_field_with_custom_fragment() {
    let input = include_str!("ir/fixtures/new-resolver-model-field-with-custom-fragment.graphql");
    let expected = include_str!("ir/fixtures/new-resolver-model-field-with-custom-fragment.expected");
    test_fixture(transform_fixture, "new-resolver-model-field-with-custom-fragment.graphql", "ir/fixtures/new-resolver-model-field-with-custom-fragment.expected", input, expected);
}

#[test]
fn query_then_fragment() {
    let input = include_str!("ir/fixtures/query-then-fragment.graphql");
    let expected = include_str!("ir/fixtures/query-then-fragment.expected");
    test_fixture(transform_fixture, "query-then-fragment.graphql", "ir/fixtures/query-then-fragment.expected", input, expected);
}

#[test]
fn recursive_fragments() {
    let input = include_str!("ir/fixtures/recursive-fragments.graphql");
    let expected = include_str!("ir/fixtures/recursive-fragments.expected");
    test_fixture(transform_fixture, "recursive-fragments.graphql", "ir/fixtures/recursive-fragments.expected", input, expected);
}

#[test]
fn transitive_implicit_dependency() {
    let input = include_str!("ir/fixtures/transitive-implicit-dependency.graphql");
    let expected = include_str!("ir/fixtures/transitive-implicit-dependency.expected");
    test_fixture(transform_fixture, "transitive-implicit-dependency.graphql", "ir/fixtures/transitive-implicit-dependency.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.
 */

use common::SourceLocationKey;
use dependency_analyzer::get_reachable_ast;
use dependency_analyzer::ReachableAst;
use fixture_tests::Fixture;
use graphql_syntax::*;
use intern::Lookup;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let parts: Vec<&str> = fixture.content.split("%definitions%").collect();

    let source_location = SourceLocationKey::standalone(fixture.file_name);
    let definitions = parse_executable(parts[0], source_location).unwrap();
    let base_definitions = parts
        .iter()
        .skip(1)
        .flat_map(|part| parse_executable(part, source_location).unwrap().definitions)
        .collect();
    let ReachableAst {
        definitions: result,
        base_fragment_names,
    } = get_reachable_ast(definitions.definitions, base_definitions);

    let mut texts = result
        .into_iter()
        .map(|def| def.name().unwrap().to_string())
        .collect::<Vec<_>>();
    texts.sort_unstable();
    texts.push("========== Base definitions ==========".to_string());
    let mut defs = base_fragment_names
        .iter()
        .map(|key| key.0.lookup())
        .collect::<Vec<_>>();
    defs.sort_unstable();
    texts.push(defs.join(", "));
    Ok(texts.join("\n"))
}


/*
 * 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::SourceLocationKey;
use dependency_analyzer::*;
use fixture_tests::Fixture;
use graphql_ir::*;
use graphql_syntax::parse_executable;
use intern::string_key::Intern;
use relay_test_schema::get_test_schema;
use relay_test_schema::get_test_schema_with_extensions;
use schema::SDLSchema;

fn format_definition(def: ExecutableDefinition) -> String {
    match def {
        ExecutableDefinition::Operation(operation) => {
            format!("Operation: {}", operation.name.item)
        }
        ExecutableDefinition::Fragment(fragment) => format!("Fragment: {}", fragment.name.item),
    }
}

// TODO: Test without using snapshot tests
pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let parts = fixture.content.split("%extensions%").collect::<Vec<_>>();

    let (content, schema): (&str, Arc<SDLSchema>) = match parts.as_slice() {
        [content] => (content, get_test_schema()),
        [content, extension_content] => {
            (content, get_test_schema_with_extensions(extension_content))
        }
        _ => panic!("Expected one optional \"%extensions%\" section in the fxiture."),
    };

    let parts: Vec<&str> = content.split("%definitions%").collect();
    let first_line: &str = content.lines().next().unwrap();

    let changed_names = first_line[1..]
        .trim()
        .split(',')
        .map(|name| name.trim())
        .filter(|name| !name.is_empty())
        .flat_map(|name| {
            // Note: this is a bit of a hack! Here, we don't know whether the stringkey represents
            // a fragment or operation name, so we mark both "a fragment named X" and "a query named
            // X" as having changed.
            vec![
                FragmentDefinitionName(name.intern()).into(),
                OperationDefinitionName(name.intern()).into(),
            ]
        })
        .collect();

    let source_location = SourceLocationKey::standalone(fixture.file_name);
    let mut asts = parse_executable(parts[0], source_location)
        .unwrap()
        .definitions;
    let mut base_names: ExecutableDefinitionNameSet = Default::default();
    for part in parts.iter().skip(1) {
        let defs = parse_executable(part, source_location).unwrap().definitions;
        for def in defs {
            base_names.insert(match &def {
                graphql_syntax::ExecutableDefinition::Operation(node) => {
                    OperationDefinitionName(node.name.clone().unwrap().value).into()
                }
                graphql_syntax::ExecutableDefinition::Fragment(node) => {
                    FragmentDefinitionName(node.name.value).into()
                }
            });
            asts.push(def);
        }
    }

    let definitions = build(&schema, &asts).unwrap();
    let result = get_reachable_ir(definitions, base_names, changed_names, &schema);

    let mut texts = result
        .into_iter()
        .map(format_definition)
        .collect::<Vec<_>>();
    texts.sort();
    Ok(texts.join("\n\n"))
}


/*
 * 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::DirectiveName;
use common::ScalarName;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use lazy_static::lazy_static;

lazy_static! {
    pub static ref RELAY_RESOLVER_DIRECTIVE_NAME: DirectiveName =
        DirectiveName("relay_resolver".intern());
    pub static ref RESOLVER_VALUE_SCALAR_NAME: ScalarName =
        ScalarName("RelayResolverValue".intern());
    pub static ref RELAY_RESOLVER_FIELD: StringKey = "RelayResolver".intern();
    pub static ref RELAY_RESOLVER_MODEL_DIRECTIVE_NAME: DirectiveName =
        DirectiveName("__RelayResolverModel".intern());
    pub static ref FRAGMENT_KEY_ARGUMENT_NAME: ArgumentName =
        ArgumentName("fragment_name".intern());
    pub static ref RELAY_RESOLVER_WEAK_OBJECT_DIRECTIVE: DirectiveName =
        DirectiveName("__RelayWeakObject".intern());
    pub static ref LIVE_ARGUMENT_NAME: ArgumentName = ArgumentName("live".intern());
    pub static ref IMPORT_NAME_ARGUMENT_NAME: ArgumentName = ArgumentName("import_name".intern());
    pub static ref HAS_OUTPUT_TYPE_ARGUMENT_NAME: ArgumentName =
        ArgumentName("has_output_type".intern());
    pub static ref IMPORT_PATH_ARGUMENT_NAME: ArgumentName = ArgumentName("import_path".intern());
    pub static ref INJECT_FRAGMENT_DATA_ARGUMENT_NAME: ArgumentName =
        ArgumentName("inject_fragment_data".intern());
    pub static ref GENERATED_FRAGMENT_ARGUMENT_NAME: ArgumentName =
        ArgumentName("generated_fragment".intern());
    pub static ref FIELD_NAME_FIELD: StringKey = "fieldName".intern();
    pub static ref ON_TYPE_FIELD: StringKey = "onType".intern();
    pub static ref ON_INTERFACE_FIELD: StringKey = "onInterface".intern();
    pub static ref EDGE_TO_FIELD: StringKey = "edgeTo".intern();
    pub static ref DEPRECATED_FIELD: StringKey = "deprecated".intern();
    pub static ref LIVE_FIELD: StringKey = "live".intern();
    pub static ref ROOT_FRAGMENT_FIELD: StringKey = "rootFragment".intern();
    pub static ref OUTPUT_TYPE_FIELD: StringKey = "outputType".intern();
    pub static ref WEAK_FIELD: StringKey = "weak".intern();
    pub static ref EMPTY_STRING: StringKey = "".intern();
    pub static ref ARGUMENT_DEFINITIONS: DirectiveName =
        DirectiveName("argumentDefinitions".intern());
    pub static ref ARGUMENT_TYPE: StringKey = "type".intern();
    pub static ref DEFAULT_VALUE: StringKey = "defaultValue".intern();
    pub static ref PROVIDER_ARG_NAME: StringKey = "provider".intern();
    // Note: this should **only** be used for resolvers! The id field for server
    // types is configurable in the config, and thus cannot be hard-coded.
    pub static ref KEY_RESOLVER_ID_FIELD: StringKey = "id".intern();
}


/*
 * 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::Location;
use common::Named;
use common::WithLocation;
use intern::string_key::StringKey;
#[derive(Debug, PartialEq)]
pub struct DocblockField {
    pub field_name: WithLocation<StringKey>,
    pub field_value: Option<WithLocation<StringKey>>,
}

impl Named for DocblockField {
    type Name = StringKey;
    fn name(&self) -> StringKey {
        self.field_name.item
    }
}

#[derive(Debug, PartialEq)]
pub enum DocblockSection {
    Field(DocblockField),
    FreeText(WithLocation<StringKey>),
}

#[derive(Debug, PartialEq)]
pub struct DocblockAST {
    pub location: Location,
    pub sections: Vec<DocblockSection>,
}

impl DocblockAST {
    pub fn find_field(&self, name: StringKey) -> Option<&DocblockField> {
        self.sections.iter().find_map(|section| match section {
            DocblockSection::Field(field) if field.name() == name => Some(field),
            _ => None,
        })
    }
}


/*
 * 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 thiserror::Error;

#[derive(Clone, Copy, Debug, Error, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum SyntaxError {
    #[error("Expected \"{expected}\".")]
    ExpectedString { expected: &'static str },

    #[error("Expected @ to be followed by a field name.")]
    ExpectedFieldName,
}


/*
 * 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 ast;
mod errors;

use std::iter::Peekable;
use std::str::Chars;

pub use ast::DocblockAST;
pub use ast::DocblockField;
pub use ast::DocblockSection;
use common::Diagnostic;
use common::DiagnosticsResult;
use common::Location;
use common::SourceLocationKey;
use common::Span;
use common::TextSource;
use common::WithLocation;
use errors::SyntaxError;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use serde::Deserialize;
use serde::Serialize;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DocblockSource(TextSource);

impl DocblockSource {
    pub fn new(text: impl Into<String>, line_index: usize, column_index: usize) -> Self {
        Self(TextSource {
            text: text.into(),
            line_index,
            column_index,
        })
    }

    pub fn text_source(&self) -> &TextSource {
        &self.0
    }

    pub fn to_text_source(self) -> TextSource {
        self.0
    }
}

type ParseResult<T> = Result<T, ()>;
/// Parses a docblock's contents.
///
/// Expects to be passed a string containing the _contents_ of a docblock (with
/// the leading `/*` and trailing `*/` already trimmed).
///
/// The structure of docblocks is not well defined. To avoid needing to be
/// opinionated, we use a relatively restricted definition for now:
///
/// * Docblocks consist of n "sections" where each section is either a key/value field, or free text.
/// * Each line must take the form: WHITE_SPACE* `*` WHITE_SPACE? LINE_CONTENT
/// * Free text sections may span one or more lines.
/// * Field sections are a _single line_ of the form: `@` FIELD_NAME WHITE_SPACE* VALUE
pub fn parse_docblock(
    source: &str,
    source_location: SourceLocationKey,
) -> DiagnosticsResult<DocblockAST> {
    DocblockParser::new(source, source_location).parse()
}

/**
 * Stateful parser that tries to model parsing of docblocks using recursive
 * descent.
 *
 * Parsing docblocks in this fashion is a bit awkward because it's not possible
 * to tokenize the input without being aware of context because we would like to
 * tokenize the input differently based upon what we are trying to parse.
 *
 * For example:
 * - Docblocks allow free text which might contain strings which should be
 *   considered tokens in other contexts.
 *
 * This is why languages designed to be expressible in a formal grammar delineate
 * strings with quotation marks.
 *
 * To account for this, we parse in a single pass, essentially treating each
 * character as a token. This allows us to easily intemperate characters
 * differently in different contexts.
 */
struct DocblockParser<'a> {
    source_location: SourceLocationKey,
    chars: Peekable<Chars<'a>>,
    offset: u32,
    errors: Vec<Diagnostic>,
    in_progress_text: Option<SpanString>,
    sections: Vec<DocblockSection>,
}

impl<'a> DocblockParser<'a> {
    fn new(source: &'a str, source_location: SourceLocationKey) -> Self {
        let chars = source.chars().peekable();
        Self {
            errors: Vec::new(),
            source_location,
            offset: 0,
            chars,
            in_progress_text: None,
            sections: Vec::new(),
        }
    }

    fn parse(mut self) -> DiagnosticsResult<DocblockAST> {
        let start = self.offset;
        // By convention most docblocks start `/**`. Since we expect the leading
        // `/*` to be trimmed before it's passed to us, we want to remove the
        // second `*` and its trailing whitespace/newline.
        if self.peek_char(&'*') {
            self.next();
            self.consume_whitespace();
        }

        let result = self.parse_sections();
        let end = self.offset;

        if self.errors.is_empty() {
            result.expect("Expected no parse errors.");
            Ok(DocblockAST {
                sections: self.sections,
                location: Location::new(
                    self.source_location,
                    /*
                     * TODO(T113385544): Investigate if this is actually a bug in Span::to_range.
                     *
                     * I honestly don't fully understand this. We use
                     * self.offset as the end position for all other spans and
                     * they render perfectly. However, if we try to show a
                     * diagnostic of the full docblock (which we do when it's
                     * missing a field) it breaks Span::to_range because the end
                     * index is greater than the index of the last char that it
                     * iterates over, so it never sets an end_position and
                     * therefore shows the end position as 0,0 and VSCode
                     * renders an inverted diagnostic (a range starting at 0,0
                     * and ending at the start position.
                     *
                     * Anyway, subtracting 1 here solves the problem for now.
                     */
                    Span::new(start, end - 1),
                ),
            })
        } else {
            Err(self.errors)
        }
    }

    /// Loop over chars, consuming a line at a time.
    fn parse_sections(&mut self) -> ParseResult<()> {
        while self.chars.peek().is_some() {
            if !self.parse_margin()? {
                // We reached the end of the input
                break;
            }
            if self.peek_char(&'@') {
                self.next();

                // The fact the encountered a field tells us the previous
                // free text section is now complete.
                self.complete_previous_section();

                self.parse_field()?;
            } else {
                let free_text = self.parse_free_text_line()?;
                if let Some(previous) = self.in_progress_text.as_mut() {
                    previous.append_line(free_text)
                } else {
                    self.in_progress_text = Some(free_text);
                }
            }
        }
        self.complete_previous_section();
        Ok(())
    }

    /// Consume the `  * ` left margin, returning true/false indicating if we
    /// read the full margin without reaching the end of the input.
    fn parse_margin(&mut self) -> ParseResult<bool> {
        self.consume_whitespace();
        if self.chars.peek().is_none() {
            return Ok(false);
        }
        self.expect_str("*")?;
        if self.peek_char(&' ') {
            self.next();
        }

        if self.chars.peek().is_none() {
            return Ok(false);
        }
        Ok(true)
    }

    fn parse_field(&mut self) -> ParseResult<()> {
        let field_name = self.parse_field_name()?;
        self.consume_non_breaking_space();
        let text = self.parse_free_text_line()?;

        let field_value = if text.string.is_empty() {
            None
        } else {
            Some(text.to_with_location(self.source_location))
        };

        self.sections.push(DocblockSection::Field(DocblockField {
            field_name: field_name.to_with_location(self.source_location),
            field_value,
        }));

        Ok(())
    }

    fn parse_field_name(&mut self) -> ParseResult<SpanString> {
        let start = self.offset;
        let name = self.take_while(is_field_name_char);
        if name.is_empty() {
            self.errors.push(Diagnostic::error(
                SyntaxError::ExpectedFieldName,
                self.current_position(),
            ));
            return Err(());
        }
        let end = self.offset;
        Ok(SpanString::new(Span::new(start, end), name))
    }

    /// Read until the end of the line.
    fn parse_free_text_line(&mut self) -> ParseResult<SpanString> {
        let start = self.offset;
        let free_text = self.take_while(|c| c != &'\n');
        let end = self.offset;
        Ok(SpanString::new(Span::new(start, end), free_text))
    }

    fn complete_previous_section(&mut self) {
        if let Some(free_text) = &self.in_progress_text {
            if !free_text.string.is_empty() {
                self.sections.push(DocblockSection::FreeText(
                    free_text.to_with_location(self.source_location),
                ));
            }
            self.in_progress_text = None;
        }
    }

    fn consume_whitespace(&mut self) {
        self.next_while(|c| c.is_whitespace());
    }

    fn consume_non_breaking_space(&mut self) {
        self.next_while(|c| c == &' ' || c == &'\t');
    }

    fn expect_str(&mut self, expected: &'static str) -> ParseResult<()> {
        for c in expected.chars() {
            if self.peek_char(&c) {
                self.next();
            } else {
                self.errors.push(Diagnostic::error(
                    SyntaxError::ExpectedString { expected },
                    self.current_position(),
                ));
                return Err(());
            }
        }
        Ok(())
    }

    fn peek_char(&mut self, c: &char) -> bool {
        self.chars.peek() == Some(c)
    }

    fn next(&mut self) {
        self.chars.next();
        self.offset += 1;
    }

    /// Advance over a string of characters matching predicate.
    fn next_while(&mut self, predicate: fn(&char) -> bool) {
        while let Some(c) = self.chars.peek() {
            if predicate(c) {
                self.next();
            } else {
                break;
            }
        }
    }

    /// Consume and return a string of characters matching predicate.
    fn take_while(&mut self, predicate: fn(&char) -> bool) -> String {
        let mut result = String::new();
        while let Some(c) = self.chars.peek() {
            if predicate(c) {
                result.push(self.chars.next().unwrap());
            } else {
                break;
            }
        }
        self.offset += result.len() as u32;
        result
    }

    fn current_position(&self) -> Location {
        Location::new(self.source_location, Span::new(self.offset, self.offset))
    }
}

fn is_field_name_char(c: &char) -> bool {
    c.is_alphanumeric() || c == &'_'
}

#[derive(Debug)]
struct SpanString {
    span: Span,
    string: String,
}

impl SpanString {
    fn new(span: Span, string: String) -> Self {
        Self { span, string }
    }
    fn append_line(&mut self, other: Self) {
        self.string.push_str("\n");
        self.string.push_str(&other.string);
        self.span.end = other.span.end;
    }
    fn to_with_location(&self, source_location: SourceLocationKey) -> WithLocation<StringKey> {
        WithLocation::from_span(source_location, self.span, self.string.clone().intern())
    }
}


/*
 * 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<<cfbc1b0c55b29bb5b8a94089f5dfc16f>>
 */

mod parse;

use parse::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn docblock_ends_mid_line() {
    let input = include_str!("parse/fixtures/docblock-ends-mid-line.ecmascript");
    let expected = include_str!("parse/fixtures/docblock-ends-mid-line.expected");
    test_fixture(transform_fixture, "docblock-ends-mid-line.ecmascript", "parse/fixtures/docblock-ends-mid-line.expected", input, expected);
}

#[test]
fn empty_block() {
    let input = include_str!("parse/fixtures/empty-block.ecmascript");
    let expected = include_str!("parse/fixtures/empty-block.expected");
    test_fixture(transform_fixture, "empty-block.ecmascript", "parse/fixtures/empty-block.expected", input, expected);
}

#[test]
fn field_followed_by_free_text() {
    let input = include_str!("parse/fixtures/field-followed-by-free-text.ecmascript");
    let expected = include_str!("parse/fixtures/field-followed-by-free-text.expected");
    test_fixture(transform_fixture, "field-followed-by-free-text.ecmascript", "parse/fixtures/field-followed-by-free-text.expected", input, expected);
}

#[test]
fn free_text_starting_with_star() {
    let input = include_str!("parse/fixtures/free-text-starting-with-star.ecmascript");
    let expected = include_str!("parse/fixtures/free-text-starting-with-star.expected");
    test_fixture(transform_fixture, "free-text-starting-with-star.ecmascript", "parse/fixtures/free-text-starting-with-star.expected", input, expected);
}

#[test]
fn invalid_docblock_invalid() {
    let input = include_str!("parse/fixtures/invalid_docblock.invalid.ecmascript");
    let expected = include_str!("parse/fixtures/invalid_docblock.invalid.expected");
    test_fixture(transform_fixture, "invalid_docblock.invalid.ecmascript", "parse/fixtures/invalid_docblock.invalid.expected", input, expected);
}

#[test]
fn invalid_field_name_invalid() {
    let input = include_str!("parse/fixtures/invalid_field_name.invalid.ecmascript");
    let expected = include_str!("parse/fixtures/invalid_field_name.invalid.expected");
    test_fixture(transform_fixture, "invalid_field_name.invalid.ecmascript", "parse/fixtures/invalid_field_name.invalid.expected", input, expected);
}

#[test]
fn missing_star_invalid() {
    let input = include_str!("parse/fixtures/missing_star.invalid.ecmascript");
    let expected = include_str!("parse/fixtures/missing_star.invalid.expected");
    test_fixture(transform_fixture, "missing_star.invalid.ecmascript", "parse/fixtures/missing_star.invalid.expected", input, expected);
}

#[test]
fn multiple_fields() {
    let input = include_str!("parse/fixtures/multiple-fields.ecmascript");
    let expected = include_str!("parse/fixtures/multiple-fields.expected");
    test_fixture(transform_fixture, "multiple-fields.ecmascript", "parse/fixtures/multiple-fields.expected", input, expected);
}

#[test]
fn simple_docblock() {
    let input = include_str!("parse/fixtures/simple_docblock.ecmascript");
    let expected = include_str!("parse/fixtures/simple_docblock.expected");
    test_fixture(transform_fixture, "simple_docblock.ecmascript", "parse/fixtures/simple_docblock.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.
 */

use common::SourceLocationKey;
use docblock_syntax::parse_docblock;
use fixture_tests::Fixture;
use graphql_test_helpers::diagnostics_to_sorted_string;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let mut content = fixture.content;
    if !content.starts_with("/*") {
        panic!("Expected fixture to start with \"/*\".")
    };
    if !content.ends_with("*/\n") {
        panic!("Expected fixture to end with \"*/\" followed by a newline.")
    }
    content = &content[2..content.len() - 3];
    parse_docblock(content, SourceLocationKey::standalone(fixture.file_name))
        .map(|x| format!("{:#?}", x))
        .map_err(|diagnostics| diagnostics_to_sorted_string(content, &diagnostics))
}


/*
 * 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::sync::*;
// use rayon::prelude::*;

// Helpers for unwrapping multiple Results to aggregate all success values or all error values.

pub fn try2<T1, T2, E>(t1: Result<T1, Vec<E>>, t2: Result<T2, Vec<E>>) -> Result<(T1, T2), Vec<E>> {
    match (t1, t2) {
        (Ok(t1), Ok(t2)) => Ok((t1, t2)),
        (Ok(_), Err(errors)) => Err(errors),
        (Err(errors), Ok(_)) => Err(errors),
        (Err(mut errors1), Err(errors2)) => {
            errors1.extend(errors2);
            Err(errors1)
        }
    }
}

pub fn try3<T1, T2, T3, E>(
    t1: Result<T1, Vec<E>>,
    t2: Result<T2, Vec<E>>,
    t3: Result<T3, Vec<E>>,
) -> Result<(T1, T2, T3), Vec<E>> {
    let mut errors = Vec::new();
    let t1 = match t1 {
        Ok(t1) => Some(t1),
        Err(e) => {
            errors.extend(e);
            None
        }
    };
    let t2 = match t2 {
        Ok(t2) => Some(t2),
        Err(e) => {
            errors.extend(e);
            None
        }
    };
    let t3 = match t3 {
        Ok(t3) => Some(t3),
        Err(e) => {
            errors.extend(e);
            None
        }
    };
    if !errors.is_empty() {
        Err(errors)
    } else {
        Ok((t1.unwrap(), t2.unwrap(), t3.unwrap()))
    }
}

pub fn try4<T1, T2, T3, T4, E>(
    t1: Result<T1, Vec<E>>,
    t2: Result<T2, Vec<E>>,
    t3: Result<T3, Vec<E>>,
    t4: Result<T4, Vec<E>>,
) -> Result<(T1, T2, T3, T4), Vec<E>> {
    let mut errors = Vec::new();
    let t1 = match t1 {
        Ok(t1) => Some(t1),
        Err(e) => {
            errors.extend(e);
            None
        }
    };
    let t2 = match t2 {
        Ok(t2) => Some(t2),
        Err(e) => {
            errors.extend(e);
            None
        }
    };
    let t3 = match t3 {
        Ok(t3) => Some(t3),
        Err(e) => {
            errors.extend(e);
            None
        }
    };
    let t4 = match t4 {
        Ok(t4) => Some(t4),
        Err(e) => {
            errors.extend(e);
            None
        }
    };
    if !errors.is_empty() {
        Err(errors)
    } else {
        Ok((t1.unwrap(), t2.unwrap(), t3.unwrap(), t4.unwrap()))
    }
}

/// Given an iterable of Result values, returns all the success values if all items are
/// `Ok` or all the error values if one or more items were `Err`.
pub fn try_all<T, E, I>(items: I) -> Result<Vec<T>, Vec<E>>
where
    I: IntoIterator<Item = Result<T, Vec<E>>>,
{
    try_map(items, |x| x)
}

/// Transforms the items of a list with a fallible transform function, returning either
/// all the transformed values if all items succeeded or a list of all errors if one or
/// more items could not be tansformed.
pub fn try_map<T, E, U, I, F>(items: I, mut f: F) -> Result<Vec<T>, Vec<E>>
where
    I: IntoIterator<Item = U>,
    F: FnMut(U) -> Result<T, Vec<E>>,
{
    let iter = items.into_iter();
    let mut errors = Vec::new();
    let mut values = Vec::with_capacity(iter.size_hint().1.unwrap_or_default());
    for item in iter {
        match f(item) {
            Ok(item_value) => values.push(item_value),
            Err(item_errors) => errors.extend(item_errors),
        }
    }
    if errors.is_empty() {
        Ok(values)
    } else {
        Err(errors)
    }
}

/// Similar to `try_map` but performs the transform in parallel.
pub fn par_try_map<T: Sync + Send, E: Sync + Send, U: Sync + Send, I, F: Sync + Send>(
    items: I,
    f: F,
) -> Result<Vec<T>, Vec<E>>
where
    I: IntoParallelIterator<Item = U> + IntoIterator<Item = U>,
    F: Fn(U) -> Result<T, Vec<E>>,
{
    let results: Vec<Result<T, Vec<E>>> = par_iter(items).map(f).collect();
    let mut errors = Vec::new();
    let mut values = Vec::with_capacity(results.len());
    for result in results {
        match result {
            Ok(item_value) => values.push(item_value),
            Err(item_errors) => errors.extend(item_errors),
        }
    }
    if errors.is_empty() {
        Ok(values)
    } else {
        Err(errors)
    }
}

#[macro_export]
macro_rules! validate {
    ($($args:expr),*) => {{
        let mut errors = Vec::new();
        $(
            match $args {
                Ok(_) => {},
                Err(e) => {
                    errors.extend(e);
                }
            }
        )*
        if errors.is_empty() {
            Ok(())
        } else {
            Err(errors)
        }
    }}
}

pub fn validate_map<T, E, U, I, F>(items: I, f: F) -> Result<(), Vec<E>>
where
    I: IntoIterator<Item = U>,
    F: FnMut(U) -> Result<T, Vec<E>>,
{
    try_map(items, f)?;
    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.
 */

mod error_combinators;

pub use error_combinators::*;


/*
 * 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.
 */

#![deny(warnings)]
#![deny(rust_2018_idioms)]
#![deny(clippy::all)]

use std::iter::Peekable;
use std::str::CharIndices;

use common::TextSource;
use docblock_syntax::DocblockSource;
use graphql_syntax::GraphQLSource;

#[derive(Clone)]
pub enum JavaScriptSourceFeature {
    GraphQL(GraphQLSource),
    Docblock(DocblockSource),
}

impl JavaScriptSourceFeature {
    pub fn text_source(&self) -> &TextSource {
        match self {
            JavaScriptSourceFeature::GraphQL(graphql_source) => graphql_source.text_source(),
            JavaScriptSourceFeature::Docblock(docblock_source) => docblock_source.text_source(),
        }
    }

    pub fn to_text_source(self) -> TextSource {
        match self {
            JavaScriptSourceFeature::GraphQL(graphql_source) => graphql_source.to_text_source(),
            JavaScriptSourceFeature::Docblock(docblock_source) => docblock_source.to_text_source(),
        }
    }
}

/// A wrapper around a peekable char iterator that tracks
/// the column and line indicies.
pub struct CharReader<'a> {
    chars: Peekable<CharIndices<'a>>,
    line_index: usize,
    column_index: usize,
}

impl<'a> CharReader<'a> {
    pub fn new(input: &'a str) -> Self {
        let chars = input.char_indices().peekable();
        CharReader {
            chars,
            line_index: 0,
            column_index: 0,
        }
    }
}

impl<'a> Iterator for CharReader<'a> {
    type Item = (usize, char);
    fn next(&mut self) -> Option<Self::Item> {
        let pair = self.chars.next();
        if let Some((_index, ch)) = pair {
            match ch {
                // Line terminators: https://www.ecma-international.org/ecma-262/#sec-line-terminators
                '\u{000A}' | '\u{000D}' | '\u{2028}' | '\u{2029}' => {
                    // <CRLF>
                    if ch == '\u{000D}' {
                        if let Some((_, ch)) = self.chars.peek() {
                            if ch == &'\u{00A}' {
                                return pair;
                            }
                        }
                    }
                    self.line_index += 1;
                    self.column_index = 0;
                }
                _ => {
                    self.column_index += 1;
                }
            }
        }
        pair
    }
}

/// Extract graphql`text` literals and @RelayResolver comments from JS-like code.
// This should work for Flow or TypeScript alike.
pub fn extract(input: &str) -> Vec<JavaScriptSourceFeature> {
    let mut res = Vec::new();
    if !input.contains("graphql") && !input.contains("@RelayResolver") {
        return res;
    }
    let mut it = CharReader::new(input);
    'code: while let Some((i, c)) = it.next() {
        match c {
            'g' => {
                for expected in ['r', 'a', 'p', 'h', 'q', 'l'] {
                    if let Some((_, c)) = it.next() {
                        if c != expected {
                            consume_identifier(&mut it);
                            continue 'code;
                        }
                    }
                }

                let mut whitespace_num: usize = 0;

                loop {
                    if let Some((_, c)) = it.next() {
                        match c {
                            '`' => {
                                break;
                            }
                            ' ' | '\n' | '\r' | '\t' => {
                                whitespace_num += 1;
                            }
                            _ => {
                                consume_identifier(&mut it);
                                continue 'code;
                            }
                        }
                    }
                }
                let start = i;
                let line_index = it.line_index;
                let column_index = it.column_index;
                let mut has_visited_first_char = false;
                for (i, c) in &mut it {
                    match c {
                        '`' => {
                            let end = i;
                            let text = &input[start + (8 + whitespace_num)..end];
                            res.push(JavaScriptSourceFeature::GraphQL(GraphQLSource::new(
                                text,
                                line_index,
                                column_index,
                            )));
                            continue 'code;
                        }
                        ' ' | '\n' | '\r' | '\t' => {}
                        'a'..='z' | 'A'..='Z' | '#' => {
                            if !has_visited_first_char {
                                has_visited_first_char = true;
                            }
                        }
                        _ => {
                            if !has_visited_first_char {
                                continue 'code;
                            }
                        }
                    }
                }
            }
            'a'..='z' | 'A'..='Z' | '_' => {
                consume_identifier(&mut it);
            }
            // Skip over template literals. Unfortunately, this isn't enough to
            // deal with nested template literals and runs a risk of skipping
            // over too much code -- so it is disabled.
            //   '`' => {
            //       while let Some((_, c)) = it.next() {
            //           match c {
            //               '`' => {
            //                   continue 'code;
            //               }
            //               '\\' => {
            //                   it.next();
            //               }
            //               _ => {}
            //           }
            //       }
            //   }
            '"' => consume_string(&mut it, '"'),
            '\'' => consume_string(&mut it, '\''),
            '/' => {
                match it.next() {
                    Some((_, '/')) => {
                        consume_line_comment(&mut it);
                    }
                    Some((_, '*')) => {
                        let start = i;
                        let line_index = it.line_index;
                        let column_index = it.column_index;
                        let mut prev_c = ' '; // arbitrary character other than *
                        let mut first = true;
                        for (i, c) in &mut it {
                            // Hack for template literals containing /*, see D21256605:
                            if first && c == '`' {
                                break;
                            }
                            first = false;
                            if prev_c == '*' && c == '/' {
                                let end = i;
                                let text = &input[start + 2..end - 1];
                                if text.contains("@RelayResolver") {
                                    res.push(JavaScriptSourceFeature::Docblock(
                                        DocblockSource::new(text, line_index, column_index),
                                    ));
                                }
                                continue 'code;
                            }
                            prev_c = c;
                        }
                    }
                    _ => {}
                }
            }
            _ => {}
        };
    }
    res
}

fn consume_identifier(it: &mut CharReader<'_>) {
    for (_, c) in it {
        match c {
            'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => {}
            _ => {
                return;
            }
        }
    }
}

fn consume_line_comment(it: &mut CharReader<'_>) {
    for (_, c) in it {
        match c {
            '\n' | '\r' => {
                break;
            }
            _ => {}
        }
    }
}

fn consume_string(it: &mut CharReader<'_>, quote: char) {
    while let Some((_, c)) = it.next() {
        match c {
            '\\' => {
                it.next();
            }
            '\'' | '"' if c == quote => {
                return;
            }
            '\n' | '\r' => {
                // Unexpected newline, terminate the string parsing to recover
                return;
            }
            _ => {}
        }
    }
}


/*
 * 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<<7414a7d21db69b967b49c36af99ace12>>
 */

mod extract;

use extract::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn comments() {
    let input = include_str!("extract/fixtures/comments.js");
    let expected = include_str!("extract/fixtures/comments.expected");
    test_fixture(transform_fixture, "comments.js", "extract/fixtures/comments.expected", input, expected);
}

#[test]
fn inline() {
    let input = include_str!("extract/fixtures/inline.js");
    let expected = include_str!("extract/fixtures/inline.expected");
    test_fixture(transform_fixture, "inline.js", "extract/fixtures/inline.expected", input, expected);
}

#[test]
fn nested_template_literals() {
    let input = include_str!("extract/fixtures/nested_template_literals.js");
    let expected = include_str!("extract/fixtures/nested_template_literals.expected");
    test_fixture(transform_fixture, "nested_template_literals.js", "extract/fixtures/nested_template_literals.expected", input, expected);
}

#[test]
fn no_graphql() {
    let input = include_str!("extract/fixtures/no_graphql.js");
    let expected = include_str!("extract/fixtures/no_graphql.expected");
    test_fixture(transform_fixture, "no_graphql.js", "extract/fixtures/no_graphql.expected", input, expected);
}

#[test]
fn quote_in_jsx() {
    let input = include_str!("extract/fixtures/quote_in_jsx.js");
    let expected = include_str!("extract/fixtures/quote_in_jsx.expected");
    test_fixture(transform_fixture, "quote_in_jsx.js", "extract/fixtures/quote_in_jsx.expected", input, expected);
}

#[test]
fn relay_resolver() {
    let input = include_str!("extract/fixtures/relay_resolver.js");
    let expected = include_str!("extract/fixtures/relay_resolver.expected");
    test_fixture(transform_fixture, "relay_resolver.js", "extract/fixtures/relay_resolver.expected", input, expected);
}

#[test]
fn relay_resolver_and_graphql() {
    let input = include_str!("extract/fixtures/relay_resolver_and_graphql.js");
    let expected = include_str!("extract/fixtures/relay_resolver_and_graphql.expected");
    test_fixture(transform_fixture, "relay_resolver_and_graphql.js", "extract/fixtures/relay_resolver_and_graphql.expected", input, expected);
}

#[test]
fn simple() {
    let input = include_str!("extract/fixtures/simple.flow");
    let expected = include_str!("extract/fixtures/simple.expected");
    test_fixture(transform_fixture, "simple.flow", "extract/fixtures/simple.expected", input, expected);
}

#[test]
fn tabbed() {
    let input = include_str!("extract/fixtures/tabbed.js");
    let expected = include_str!("extract/fixtures/tabbed.expected");
    test_fixture(transform_fixture, "tabbed.js", "extract/fixtures/tabbed.expected", input, expected);
}

#[test]
fn template_literal() {
    let input = include_str!("extract/fixtures/template_literal.js");
    let expected = include_str!("extract/fixtures/template_literal.expected");
    test_fixture(transform_fixture, "template_literal.js", "extract/fixtures/template_literal.expected", input, expected);
}

#[test]
fn with_space() {
    let input = include_str!("extract/fixtures/with_space.js");
    let expected = include_str!("extract/fixtures/with_space.expected");
    test_fixture(transform_fixture, "with_space.js", "extract/fixtures/with_space.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.
 */

use extract_graphql::extract;
use extract_graphql::JavaScriptSourceFeature;
use fixture_tests::Fixture;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let features = extract(fixture.content);
    Ok(features
        .into_iter()
        .map(|feature| match feature {
            JavaScriptSourceFeature::Docblock(docblock_source) => {
                let s = docblock_source.text_source();
                format!(
                    "docblock - line: {}, column: {}, text: <{}>",
                    s.line_index, s.column_index, s.text
                )
            }
            JavaScriptSourceFeature::GraphQL(graphql_source) => {
                let s = graphql_source.text_source();
                format!(
                    "graphql - line: {}, column: {}, text: <{}>",
                    s.line_index, s.column_index, s.text
                )
            }
        })
        .collect::<Vec<_>>()
        .join("\n"))
}


/*
 * 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.
 */

#![deny(warnings)]
#![deny(rust_2018_idioms)]
#![deny(clippy::all)]

//! This crate aims to make it easy to write integration tests for any function
//! that transforms an input string to some output string.
//!
//! To write a test for a set of input files for some crate example-crate we
//! need the following parts:
//!
//! ```ignore
//! example-crate/
//!   src/ # typical source
//!   tests/ # directory of integration tests
//!     first_transform_test.rs # generated (see below)
//!     first_transform/
//!       mod.rs # exports the transform to test
//!       fixtures/
//!         fixture_one.txt
//!         fixture_one.expected
//!         fixture_two.txt
//!         fixture_two.expected
//! ```
//!
//! `tests/first_transform/mod.rs` exports the transform to test, for example:
//!
//! ```ignore
//! pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
//!   Ok(fixture.to_uppercase())
//! }
//! ```
//!
//! Once the fixtures are in place or new ones are added, `fixture-tests` can
//! be run as a binary to update the fixtures.
//!
//! ```ignore
//! cargo run -p fixture-tests "example-create/tests/first_transform"
//! ```
//!
//! *FB-internal: see `scripts/generate_fixture_tests.sh` to generate all.*
//
//! *FB-internal: use buck run //relay/oss/crates/fixture-tests:fixture-tests-bin -- <path to tests dir>
//! *FB-internal: if you don't want to use cargo run. This is useful for development on a dev-server or
//! *FB-internal: or machines w/o cargo installed.

mod print_diff;

use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::sync::Arc;

use lazy_static::lazy_static;
use parking_lot::Mutex;

lazy_static! {
    static ref LOCK: Arc<Mutex<usize>> = Arc::new(Mutex::new(0));
}

/// Passed to the `transform_fixture` from the test as the input to transform.
pub struct Fixture<'a> {
    /// File name that can be used if the transform needs it to print error messages, etc.
    pub file_name: &'a str,
    /// The text content of the fixture.
    pub content: &'a str,
}

/// This is an internal function and is typically called from generated code
/// containing one test per fixture.
pub fn test_fixture<T, U, V>(
    transform: T,
    input_file_name: &str,
    expected_file_name: &str,
    input: &str,
    expected: &str,
) where
    T: FnOnce(&Fixture<'_>) -> Result<U, V>,
    U: std::fmt::Display,
    V: std::fmt::Display,
{
    let fixture = Fixture {
        file_name: input_file_name,
        content: input,
    };
    let expect_ok = !input.contains("expected-to-throw");
    let actual_result: Result<U, V>;
    {
        let _guard = LOCK.lock();
        colored::control::set_override(false);
        actual_result = transform(&fixture);
        colored::control::unset_override();
    }

    let actual = match &actual_result {
        Ok(output) => format!(
            "==================================== INPUT ====================================
{}
==================================== OUTPUT ===================================
{}
",
            &fixture.content.trim(),
            output,
        ),
        Err(output) => format!(
            "==================================== INPUT ====================================
{}
==================================== ERROR ====================================
{}
",
            fixture.content.trim(),
            output
        ),
    };

    match actual_result {
        Ok(_) if !expect_ok => {
            panic!(
                "Expected transform to error but it succeeded, remove 'expected-to-throw' if this is expected"
            );
        }
        Err(error) if expect_ok => {
            panic!(
                "Expected transform to succeed but it failed with, use 'expected-to-throw' if this is expected. Error:
#############################################################################
{}
#############################################################################
",
                error
            );
        }
        _ => {}
    };

    let actual = format!("{}\n", actual.trim_end());
    if actual != expected {
        {
            let _guard = LOCK.lock();
            print_diff::print_diff(expected, &actual);
        }

        if env::var_os("UPDATE_SNAPSHOTS").is_some() {
            let file_name = format!("tests/{}", expected_file_name);
            File::create(&file_name)
                .unwrap_or_else(|_| {
                    panic!(
                        "Unable to create {}/{}",
                        env::current_dir().unwrap().to_str().unwrap(),
                        file_name
                    )
                })
                .write_all(actual.as_bytes())
                .unwrap();
        } else {
            panic!("Snapshot did not match. Run with UPDATE_SNAPSHOTS=1 to update.");
        }
    }
}


/*
 * 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.
 */

#![deny(clippy::all)]

use std::collections::HashMap;
use std::fs;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::path::PathBuf;

use clap::Parser;
use colored::Colorize;
use signedsource::sign_file;
use signedsource::SIGNING_TOKEN;

#[derive(Debug, Parser)]
#[clap(name = "fixture-tests", about = "Generates fixture tests.")]
struct Options {
    /// List of directories, each should contain a `/fixtures` subdirectory
    /// from which a test file will be generated
    #[clap(name = "DIR")]
    dirs: Vec<PathBuf>,
}

#[derive(Debug)]
struct TestCase {
    name: String,
    input: Option<PathBuf>,
    expected: Option<PathBuf>,
}

const EXPECTED_EXTENSION: &str = "expected";

fn main() {
    let opt = Options::parse();

    for dir in opt.dirs {
        let test_name = dir.file_name().unwrap().to_str().unwrap();
        let fixtures_dir = dir.join("fixtures");
        let paths = fs::read_dir(&fixtures_dir)
            .unwrap_or_else(|_| panic!("Fixtures dir does not exist: {:?}", &fixtures_dir));
        let mut test_cases: HashMap<String, TestCase> = HashMap::new();
        for dir_entry in paths {
            let path = dir_entry.unwrap().path();
            if path.extension().is_none() {
                continue;
            }
            let name = sanitize_identifier(path.file_stem().unwrap().to_str().unwrap());
            let mut test_case = test_cases.entry(name.clone()).or_insert_with(|| TestCase {
                name,
                input: None,
                expected: None,
            });
            if path.extension().unwrap() == EXPECTED_EXTENSION {
                if let Some(ref previous) = test_case.expected {
                    panic!("Conflicting fixture name, {:?} and {:?}", previous, path);
                }
                test_case.expected = Some(path);
            } else {
                if let Some(ref previous) = test_case.input {
                    panic!("Conflicting fixture name, {:?} and {:?}", previous, path);
                }
                test_case.input = Some(path);
            }
        }
        for mut test_case in test_cases.values_mut() {
            if test_case.expected.is_none() {
                if let Some(ref input) = test_case.input {
                    let mut expected = input.clone();
                    expected.set_extension(EXPECTED_EXTENSION);
                    File::create(&expected)
                        .unwrap()
                        .write_all(
                            "\x40nocommit\nRun snapshot tests with UPDATE_SNAPSHOTS=1 to update this new file.\n"
                            .as_bytes(),
                        )
                        .unwrap();
                    test_case.expected = Some(expected);
                }
            }
        }
        let mut test_cases: Vec<(_, _)> = test_cases.into_iter().collect();
        test_cases.sort_by_key(|entry| entry.0.to_owned());

        let test_cases = test_cases
            .into_iter()
            .map(|(_, test_case)| {
                let test_case_name = &test_case.name;
                format!(
                    r#"#[test]
fn {0}() {{
    let input = include_str!("{1}/fixtures/{2}");
    let expected = include_str!("{1}/fixtures/{3}");
    test_fixture(transform_fixture, "{2}", "{1}/fixtures/{3}", input, expected);
}}"#,
                    test_case.name,
                    &test_name,
                    test_case
                        .input
                        .unwrap_or_else(|| panic!(
                            "Expected input for test {:?} to exist",
                            test_case_name
                        ))
                        .file_name()
                        .and_then(|x| x.to_str())
                        .unwrap(),
                    test_case
                        .expected
                        .unwrap_or_else(|| panic!(
                            "Expected output for test {:?} to exist",
                            test_case_name
                        ))
                        .file_name()
                        .and_then(|x| x.to_str())
                        .unwrap()
                )
            })
            .collect::<Vec<_>>()
            .join("\n\n");

        let mut file = File::create(
            dir.parent()
                .unwrap()
                .join(format!("{}_test.rs", &test_name)),
        )
        .unwrap();

        // Slightly hacky way to find out if these fixture tests are in the OSS
        // directory. This test should work on GitHub and in the internal repo.
        let is_oss = dir
            .components()
            .any(|comp| comp == std::path::Component::Normal("crates".as_ref()));

        let header = if is_oss {
            format!(
                "/*
 * 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.
 *
 * {signing_token}
 */
",
                signing_token = SIGNING_TOKEN,
            )
        } else {
            format!(
                "// {signing_token}
// Generated by $ cargo run -p fixture-tests -- {dir_name}
",
                signing_token = SIGNING_TOKEN,
                dir_name = dir.display(),
            )
        };

        let content = format!(
            "{header}
mod {test_name};

use {test_name}::transform_fixture;
use fixture_tests::test_fixture;

{test_cases}
",
            header = header,
            test_name = &test_name,
            test_cases = test_cases,
        );
        file.write_all(sign_file(&content).as_bytes()).unwrap();
        check_targets_file(&dir);
    }
}

fn check_targets_file(test_dir: &Path) {
    let targets_path = test_dir.parent().unwrap().parent().unwrap().join("TARGETS");
    let targets_content = match std::fs::read_to_string(&targets_path) {
        Ok(content) => content,
        Err(_) => return,
    };

    let expected_substr = format!(
        "\"tests/{}_test.rs\"",
        test_dir.file_stem().unwrap().to_str().unwrap()
    );

    if !targets_content.contains(&expected_substr) {
        eprintln!(
            "{}",
            format!(
                "WARNING: expected {:?} to contain substring {}",
                targets_path, expected_substr
            )
            .yellow()
        );
    }
}

fn sanitize_identifier(input: &str) -> String {
    input
        .chars()
        .map(|chr| match chr {
            'a'..='z' | '0'..='9' | '_' => chr,
            'A'..='Z' => chr.to_ascii_lowercase(),
            _ => '_',
        })
        .collect()
}


/*
 * 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 colored::Colorize;
use diff::Result::*;

/// Prints a diff between the expected and actual strings to stdout.
pub(crate) fn print_diff(expected: &str, actual: &str) {
    let changes = diff::lines(expected, actual);
    for (line_index, change) in changes.iter().enumerate() {
        let formatted_line = match change {
            Both(context, _context) => {
                // Print 1 line of context
                let prev_is_change = line_index > 0 && is_change(&changes[line_index - 1]);
                let next_is_change = changes.get(line_index + 1).map_or(false, is_change);
                if prev_is_change || next_is_change {
                    format!("| {}", context).dimmed()
                } else {
                    continue;
                }
            }
            Left(removed) => format!("- {}", removed).red(),
            Right(added) => format!("+ {}", added).green(),
        };
        println!("{:4} {}", line_index + 1, formatted_line);
    }
}

fn is_change<T>(result: &diff::Result<T>) -> bool {
    matches!(result, Left(_) | Right(_))
}


/*
 * 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<<759fa81dcfd359632faaf5a2bd8f9555>>
 */

mod uppercase;

use uppercase::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn hello() {
    let input = include_str!("uppercase/fixtures/hello.txt");
    let expected = include_str!("uppercase/fixtures/hello.expected");
    test_fixture(transform_fixture, "hello.txt", "uppercase/fixtures/hello.expected", input, expected);
}

#[test]
fn world() {
    let input = include_str!("uppercase/fixtures/world.txt");
    let expected = include_str!("uppercase/fixtures/world.expected");
    test_fixture(transform_fixture, "world.txt", "uppercase/fixtures/world.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.
 */

use fixture_tests::Fixture;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    Ok(fixture.content.to_uppercase())
}


/*
 * 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::Location;
use common::SourceLocationKey;
use common::Span;
use common::TextSource;
use graphql_cli::DiagnosticPrinter;

const EXAMPLE: &str = "fragment Example on User {
  photo(size: 40) {
    url
  }
}";

fn main() {
    let printer =
        DiagnosticPrinter::new(|_| Some(TextSource::from_whole_document(EXAMPLE.to_string())));

    let span_photo = Span::new(29, 34);
    let span_type = Span::new(20, 24);

    let source_location = SourceLocationKey::standalone("path/to/example.graphql");
    let location_field = Location::new(source_location, span_photo);
    let location_type = Location::new(source_location, span_type);

    let mut res = String::new();
    let diagnostic = Diagnostic::error("Field `photo` is not defined type `User`", location_field)
        .annotate("type defined here", location_type);
    printer.write_diagnostic(&mut res, &diagnostic).unwrap();
    print!("{}", res);
}


/*
 * 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::ops::Range;

use graphql_cli::SourcePrinter;

const EXAMPLE: &str = "fragment Example on User {
  photo(size: 40) {
    url
  }
}
some
more
lines

fragment Test on User {
  photo @
}
";

fn main() {
    // println!("char index, byte index, char");
    // for (ci, (bi, chr)) in EXAMPLE.char_indices().enumerate() {
    //     println!("{:>2}, {:>2}: {:?}", ci, bi, chr);
    // }

    let test_cases: Vec<Range<usize>> = vec![
        // first char
        0..1,
        // second char
        1..2,
        // photo
        29..34,
        // new line
        46..47,
        // outer {} block
        25..60,
        // @
        111..111,
    ];
    let printer = SourcePrinter::default();
    for test_case in test_cases.into_iter() {
        let mut res = String::new();
        printer
            .write_span(&mut res, test_case.into(), EXAMPLE, 0)
            .unwrap();
        println!("{}\n ---------\n", res);
    }
}


/*
 * 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::fmt::Write;

use colored::Colorize;
use common::Diagnostic;
use common::Location;
use common::SourceLocationKey;
use common::TextSource;

use crate::SourcePrinter;
use crate::Style;
use crate::Styles;

pub struct DiagnosticPrinter<T: Sources> {
    sources: T,
}

impl<TSources: Sources> DiagnosticPrinter<TSources> {
    pub fn new(sources: TSources) -> Self {
        Self { sources }
    }

    pub fn diagnostics_to_string(&self, diagnostics: &[Diagnostic]) -> String {
        diagnostics
            .iter()
            .map(|d| self.diagnostic_to_string(d))
            .collect::<Vec<String>>()
            .join("\n")
    }

    pub fn diagnostic_to_string(&self, diagnostic: &Diagnostic) -> String {
        let mut printed = String::new();
        self.write_diagnostic(&mut printed, diagnostic).unwrap();
        printed
    }

    pub fn write_diagnostic<W: Write>(
        &self,
        writer: &mut W,
        diagnostic: &Diagnostic,
    ) -> std::fmt::Result {
        let (message, text_color): (String, Style) = match diagnostic.severity() {
            common::DiagnosticSeverity::ERROR => {
                (format!("âœ–ï¸Ž {}", diagnostic.message()), Styles::red)
            }
            common::DiagnosticSeverity::WARNING => {
                (format!("ï¸Žâš  {}", diagnostic.message()), Styles::yellow)
            }
            common::DiagnosticSeverity::INFORMATION | common::DiagnosticSeverity::HINT => {
                (format!("â„¹ {}", diagnostic.message()), Styles::blue)
            }
            _ => (format!("â„¹ {}", diagnostic.message()), Styles::blue),
        };

        writeln!(writer, "{}\n", text_color(message))?;
        self.write_source(writer, diagnostic.location(), text_color)?;

        for related_information in diagnostic.related_information() {
            writeln!(
                writer,
                "\n{}\n",
                text_color(format!("  â„¹ï¸Ž {}", related_information.message)),
            )?;
            self.write_source(writer, related_information.location, text_color)?;
        }
        Ok(())
    }

    /// Writes the file path and slice of the source code for the given location.
    fn write_source<W: Write>(
        &self,
        writer: &mut W,
        location: Location,
        highlight_color: Style,
    ) -> std::fmt::Result {
        let source_printer = SourcePrinter::default();
        if let Some(source) = self.sources.get(location.source_location()) {
            let range = source.to_span_range(location.span());
            writeln!(
                writer,
                "  {}{}",
                location.source_location().path().underline(),
                format!(":{}:{}", range.start.line + 1, range.start.character + 1).dimmed()
            )?;
            source_printer.write_span_with_highlight_style(
                writer,
                location.span(),
                &source.text,
                source.line_index,
                highlight_color,
            )?;
        } else {
            writeln!(
                writer,
                "{}: <missing source>",
                location.source_location().path()
            )?;
        }
        Ok(())
    }
}

pub trait Sources {
    fn get(&self, source_location: SourceLocationKey) -> Option<TextSource>;
}

impl<F> Sources for F
where
    F: Fn(SourceLocationKey) -> Option<TextSource>,
{
    fn get(&self, source_location: SourceLocationKey) -> Option<TextSource> {
        self(source_location)
    }
}


/*
 * 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 diagnostic_printer;
mod source_printer;
mod text_style;

pub use diagnostic_printer::DiagnosticPrinter;
pub use diagnostic_printer::Sources;
pub use source_printer::SourcePrinter;
pub use text_style::Style;
pub use text_style::Styles;


/*
 * 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 colored::*;

pub type Style = fn(String) -> ColoredString;

macro_rules! create_style {
    ($name:ident) => {
        pub fn $name(s: String) -> ColoredString {
            s.$name()
        }
    };
}

/// Free function wrappers over colored::Colorized methods
pub struct Styles;
impl Styles {
    create_style!(red);
    create_style!(yellow);
    create_style!(blue);
    create_style!(bold);
    create_style!(dimmed);
    create_style!(underline);
    create_style!(strikethrough);
}


/*
 * 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::any::Any;
use std::fmt;
use std::hash::Hash;

pub trait AssociatedData: Send + Sync + fmt::Debug + AsAny {
    fn clone_box(&self) -> Box<dyn AssociatedData>;
    fn eq_box(&self, other: &Box<dyn AssociatedData>) -> bool;
    fn hash_box(&self) -> u64;
}
impl dyn AssociatedData {
    #[inline]
    pub fn downcast_ref<T: AssociatedData>(&self) -> Option<&T> {
        AsAny::as_any(self).downcast_ref::<T>()
    }
}

impl Clone for Box<dyn AssociatedData> {
    fn clone(&self) -> Self {
        self.clone_box()
    }
}

impl PartialEq for Box<dyn AssociatedData> {
    fn eq(&self, other: &Self) -> bool {
        self.eq_box(other)
    }
}

impl Hash for Box<dyn AssociatedData> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        // What's going on here?
        // Typically a `hash()` implementation should pass the `Hasher` down
        // and call `field.hash(hasher)` on all fields to hash. I couldn't
        // get that to work, because `H: Hasher` is not Sized which makes this
        // not a trait object.
        // https://doc.rust-lang.org/reference/types/trait-object.html
        // Instead, we just use hash internally the data fixed with `fnv`
        // and then hash that hash into the outer hasher. Not ideal, but makes
        // this work.
        self.hash_box().hash(state);
    }
}

impl Eq for Box<dyn AssociatedData> {}

#[macro_export]
macro_rules! associated_data_impl {
    ($name:ident) => {
        impl $crate::AssociatedData for $name {
            fn clone_box(&self) -> Box<dyn $crate::AssociatedData> {
                Box::new(self.clone())
            }

            fn eq_box(&self, other: &Box<dyn $crate::AssociatedData>) -> bool {
                other
                    .downcast_ref::<Self>()
                    .map_or(false, |other| other == self)
            }

            fn hash_box(&self) -> u64 {
                use std::hash::Hash;
                use std::hash::Hasher;

                use $crate::reexport::AsAny;
                use $crate::reexport::FnvHasher;
                let mut state = FnvHasher::default();
                self.hash(&mut state);
                self.as_any().type_id().hash(&mut state);
                state.finish()
            }
        }

        impl Into<$crate::Directive> for $name {
            fn into(self) -> $crate::Directive {
                $crate::Directive {
                    name: $crate::reexport::WithLocation::generated(Self::directive_name()),
                    arguments: Vec::new(),
                    data: Some(Box::new(self)),
                }
            }
        }

        impl $name {
            pub fn directive_name() -> common::DirectiveName {
                static DIRECTIVE_NAME: $crate::reexport::Lazy<common::DirectiveName> =
                    $crate::reexport::Lazy::new(|| {
                        use common::DirectiveName;
                        use $crate::reexport::string_key::Intern;
                        DirectiveName(concat!("__", stringify!($name)).intern())
                    });
                return *DIRECTIVE_NAME;
            }

            #[allow(dead_code)]
            pub fn find(directives: &[$crate::Directive]) -> Option<&Self> {
                use $crate::reexport::NamedItem;
                directives.named(Self::directive_name()).map(|directive| {
                    directive
                        .data
                        .as_ref()
                        .expect(concat!(
                            "missing data on @__",
                            stringify!($name),
                            " directive"
                        ))
                        .downcast_ref::<Self>()
                        .expect(concat!(
                            "data on @__",
                            stringify!($name),
                            " directive not of right type"
                        ))
                })
            }
        }
    };
}

/// Internal helper trait to add an `as_any()` method to every type we need it
/// on.
pub trait AsAny: Any {
    fn as_any(&self) -> &dyn Any;
}

impl<T: Any> AsAny for T {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

#[cfg(test)]
mod tests {
    use std::collections::hash_map::RandomState;
    use std::hash::BuildHasher;
    use std::hash::Hasher;

    use once_cell::sync::Lazy;

    use super::*;

    #[test]
    fn test_hash() {
        #[derive(Debug, PartialEq, Eq, Clone, Hash)]
        struct Foo(u8);
        associated_data_impl!(Foo);

        #[derive(Debug, PartialEq, Eq, Clone, Hash)]
        struct Bar(u8);
        associated_data_impl!(Bar);

        let boxed_foo_1: Box<dyn AssociatedData> = Box::new(Foo(1));
        let boxed_foo_2: Box<dyn AssociatedData> = Box::new(Foo(2));
        let boxed_bar_1: Box<dyn AssociatedData> = Box::new(Bar(1));

        static BUILD_HASHER: Lazy<RandomState> = Lazy::new(RandomState::new);
        fn hash<T: Hash>(x: T) -> u64 {
            let mut hasher = BUILD_HASHER.build_hasher();
            x.hash(&mut hasher);
            hasher.finish()
        }

        assert_eq!(
            hash(&boxed_foo_1),
            hash(&boxed_foo_1),
            "same value should hash same"
        );
        assert_ne!(
            hash(&boxed_foo_1),
            hash(&boxed_foo_2),
            "different value should hash different"
        );
        assert_eq!(
            hash(Foo(1)),
            hash(Bar(1)),
            "same value unwrapped hashes to same even for different types"
        );
        assert_ne!(
            hash(&boxed_foo_1),
            hash(&boxed_bar_1),
            "same value wrapped hashes differently"
        );
    }
}