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

254 lines
6.2 KiB
Rust
Raw Normal View History

2020-09-06 05:38:31 +00:00
use crate::parser::types::{
2020-04-05 08:00:26 +00:00
Definition, Document, FragmentDefinition, FragmentSpread, OperationDefinition,
};
2020-09-06 05:38:31 +00:00
use crate::validation::utils::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();
2020-09-06 05:38:31 +00:00
for def in &doc.definitions {
if let Definition::Operation(operation_definition) = def {
2020-09-06 06:16:36 +00:00
self.find_reachable_fragments(
&Scope::Operation(
operation_definition
.node
.name
.as_ref()
.map(|name| &*name.node),
),
&mut reachable,
);
2020-04-05 08:00:26 +00:00
}
}
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
) {
2020-09-06 06:16:36 +00:00
self.current_scope = Some(Scope::Operation(
operation_definition
.node
.name
.as_ref()
.map(|name| &*name.node),
));
2020-04-05 08:00:26 +00:00
}
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-09-06 05:38:31 +00:00
self.current_scope = Some(Scope::Fragment(&fragment_definition.node.name.node));
2020-09-06 06:16:36 +00:00
self.defined_fragments
.insert((&fragment_definition.node.name.node, fragment_definition.pos));
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-09-06 05:38:31 +00:00
.push(&fragment_spread.node.fragment_name.node);
2020-04-05 08:00:26 +00:00
}
}
}
#[cfg(test)]
mod tests {
use super::*;
pub fn factory<'a>() -> NoUnusedFragments<'a> {
NoUnusedFragments::default()
}
#[test]
fn all_fragment_names_are_used() {
2020-05-29 09:29:15 +00:00
expect_passes_rule!(
2020-04-05 08:00:26 +00:00
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() {
2020-05-29 09:29:15 +00:00
expect_passes_rule!(
2020-04-05 08:00:26 +00:00
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() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
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() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
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() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
query Foo {
human(id: 4) {
...bar
}
}
fragment foo on Human {
name
}
"#,
);
2020-03-09 10:05:52 +00:00
}
}