async-graphql/src/validation/rules/no_unused_fragments.rs

244 lines
6.0 KiB
Rust
Raw Normal View History

use crate::parser::query::{
2020-04-05 08:00:26 +00:00
Definition, Document, FragmentDefinition, FragmentSpread, OperationDefinition,
};
use crate::validation::utils::{operation_name, Scope};
use crate::validation::visitor::{Visitor, VisitorContext};
2020-05-10 02:59:51 +00:00
use crate::{Pos, Positioned};
2020-04-05 08:00:26 +00:00
use std::collections::{HashMap, HashSet};
2020-03-09 10:05:52 +00:00
#[derive(Default)]
pub struct NoUnusedFragments<'a> {
2020-04-05 08:00:26 +00:00
spreads: HashMap<Scope<'a>, Vec<&'a str>>,
defined_fragments: HashSet<(&'a str, Pos)>,
current_scope: Option<Scope<'a>>,
}
impl<'a> NoUnusedFragments<'a> {
fn find_reachable_fragments(&self, from: &Scope<'a>, result: &mut HashSet<&'a str>) {
if let Scope::Fragment(name) = *from {
if result.contains(name) {
return;
} else {
result.insert(name);
}
}
if let Some(spreads) = self.spreads.get(from) {
for spread in spreads {
self.find_reachable_fragments(&Scope::Fragment(spread), result)
}
}
}
2020-03-09 10:05:52 +00:00
}
impl<'a> Visitor<'a> for NoUnusedFragments<'a> {
2020-03-22 08:45:59 +00:00
fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, doc: &'a Document) {
2020-04-05 08:00:26 +00:00
let mut reachable = HashSet::new();
for def in doc.definitions() {
if let Definition::Operation(operation_definition) = &def.node {
2020-04-05 08:00:26 +00:00
let (name, _) = operation_name(operation_definition);
self.find_reachable_fragments(&Scope::Operation(name), &mut reachable);
}
}
for (fragment_name, pos) in &self.defined_fragments {
if !reachable.contains(fragment_name) {
ctx.report_error(
vec![*pos],
format!(r#"Fragment "{}" is never used"#, fragment_name),
);
2020-03-09 10:05:52 +00:00
}
}
}
2020-04-05 08:00:26 +00:00
fn enter_operation_definition(
&mut self,
_ctx: &mut VisitorContext<'a>,
2020-05-10 02:59:51 +00:00
operation_definition: &'a Positioned<OperationDefinition>,
2020-04-05 08:00:26 +00:00
) {
let (op_name, _) = operation_name(operation_definition);
self.current_scope = Some(Scope::Operation(op_name));
}
fn enter_fragment_definition(
&mut self,
_ctx: &mut VisitorContext<'a>,
2020-05-10 02:59:51 +00:00
fragment_definition: &'a Positioned<FragmentDefinition>,
2020-04-05 08:00:26 +00:00
) {
2020-05-16 13:14:26 +00:00
self.current_scope = Some(Scope::Fragment(fragment_definition.name.as_str()));
2020-04-05 08:00:26 +00:00
self.defined_fragments.insert((
2020-05-16 13:14:26 +00:00
fragment_definition.name.as_str(),
fragment_definition.position(),
2020-04-05 08:00:26 +00:00
));
}
2020-03-09 10:05:52 +00:00
fn enter_fragment_spread(
&mut self,
2020-03-22 08:45:59 +00:00
_ctx: &mut VisitorContext<'a>,
2020-05-10 02:59:51 +00:00
fragment_spread: &'a Positioned<FragmentSpread>,
2020-03-09 10:05:52 +00:00
) {
2020-04-05 08:00:26 +00:00
if let Some(ref scope) = self.current_scope {
self.spreads
.entry(scope.clone())
.or_insert_with(Vec::new)
2020-05-16 13:14:26 +00:00
.push(fragment_spread.fragment_name.as_str());
2020-04-05 08:00:26 +00:00
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::validation::test_harness::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> NoUnusedFragments<'a> {
NoUnusedFragments::default()
}
#[test]
fn all_fragment_names_are_used() {
expect_passes_rule(
factory,
r#"
{
human(id: 4) {
...HumanFields1
... on Human {
...HumanFields2
}
}
}
fragment HumanFields1 on Human {
name
...HumanFields3
}
fragment HumanFields2 on Human {
name
}
fragment HumanFields3 on Human {
name
}
"#,
);
}
#[test]
fn all_fragment_names_are_used_by_multiple_operations() {
expect_passes_rule(
factory,
r#"
query Foo {
human(id: 4) {
...HumanFields1
}
}
query Bar {
human(id: 4) {
...HumanFields2
}
}
fragment HumanFields1 on Human {
name
...HumanFields3
}
fragment HumanFields2 on Human {
name
}
fragment HumanFields3 on Human {
name
}
"#,
);
}
#[test]
fn contains_unknown_fragments() {
expect_fails_rule(
factory,
r#"
query Foo {
human(id: 4) {
...HumanFields1
}
}
query Bar {
human(id: 4) {
...HumanFields2
}
}
fragment HumanFields1 on Human {
name
...HumanFields3
}
fragment HumanFields2 on Human {
name
}
fragment HumanFields3 on Human {
name
}
fragment Unused1 on Human {
name
}
fragment Unused2 on Human {
name
}
"#,
);
}
#[test]
fn contains_unknown_fragments_with_ref_cycle() {
expect_fails_rule(
factory,
r#"
query Foo {
human(id: 4) {
...HumanFields1
}
}
query Bar {
human(id: 4) {
...HumanFields2
}
}
fragment HumanFields1 on Human {
name
...HumanFields3
}
fragment HumanFields2 on Human {
name
}
fragment HumanFields3 on Human {
name
}
fragment Unused1 on Human {
name
...Unused2
}
fragment Unused2 on Human {
name
...Unused1
}
"#,
);
}
#[test]
fn contains_unknown_and_undef_fragments() {
expect_fails_rule(
factory,
r#"
query Foo {
human(id: 4) {
...bar
}
}
fragment foo on Human {
name
}
"#,
);
2020-03-09 10:05:52 +00:00
}
}