text: use std::ops::Deref;
  use std::path::{Path, PathBuf};
  
  use proc_macro2::Span;
  use serde::{Deserialize, Serialize};
  
  use crate::transformer::TransformContext;
  
  /// description of a single mutation baked into the code with a given id
  #[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
  pub struct BakedMutation {
      id: usize,
      // id of the mutator that generates this mutation
      mutator_id: usize,
      mutation: Mutation,
  }
  
  // TODO: document fields and getters
  /// Mutation in source code
  #[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
  pub struct Mutation {
      pub impl_name: Option<String>,
      pub fn_name: Option<String>,
      pub mutator: String,
      pub original_code: String,
      pub mutated_code: String,
      pub source_file: PathBuf,
      pub location_in_file: String,
  }
  
  impl Mutation {
      pub fn new_spanned(
          context: &TransformContext,
          mutator: String,
          original_code: String,
          mutated_code: String,
          span: Span,
      ) -> Self {
          let impl_name = context.impl_name.clone();
          let fn_name = context.fn_name.clone();
          let start = span.start();
          let end = span.end();
          let source_file = span.unwrap().source_file().path();
          let location_in_file = format!(
              "{}:{}-{}:{}",
              start.line, start.column, end.line, end.column
          );
  
          Self {
              impl_name,
              fn_name,
              mutator,
              original_code,
              mutated_code,
              source_file,
              location_in_file,
          }
      }
  
      /// Give the mutation an id and mutator id.
      ///
      /// This creates a baked mutation.
      pub fn with_id(self, id: usize, mutator_id: usize) -> BakedMutation {
          BakedMutation {
              id,
              mutator_id,
              mutation: self,
          }
      }
  
      /// construct a string representation of the mutation
      pub fn mutation_description(&self) -> String {
          if self.mutated_code.is_empty() {
              format!("remove `{}`", &self.original_code)
          } else if self.original_code.is_empty() {
              format!("insert `{}`", &self.mutated_code)
          } else {
              format!(
                  "replace `{}` with `{}`",
                  &self.original_code, &self.mutated_code,
              )
          }
      }
  
      pub fn context_description_in_brackets(&self) -> String {
          match (&self.fn_name, &self.impl_name) {
              (None, None) => format!(""),
              (Some(fn_name), None) => format!("(fn {})", fn_name),
              (None, Some(impl_name)) => format!("(impl {})", impl_name),
              (Some(fn_name), Some(impl_name)) => format!("(fn {}::{})", impl_name, fn_name),
          }
      }
  }
  
  impl BakedMutation {
      pub fn id(&self) -> usize {
          self.id
      }
  
      pub fn mutator_id(&self) -> usize {
          self.mutator_id
      }
  
      pub fn mutator_name(&self) -> &str {
          self.mutation.mutator.deref()
      }
  
      pub fn fn_name(&self) -> Option<&str> {
          // TODO: use Option::deref instead
          self.mutation.fn_name.as_ref().map(String::deref)
      }
  
      pub fn original_code(&self) -> &str {
          self.mutation.original_code.deref()
      }
  
      pub fn mutated_code(&self) -> &str {
          self.mutation.mutated_code.deref()
      }
  
      pub fn source_file(&self) -> &Path {
          self.mutation.source_file.deref()
      }
      pub fn location_in_file(&self) -> &str {
          self.mutation.location_in_file.deref()
      }
      pub fn mutation_description(&self) -> String {
          self.mutation.mutation_description()
      }
      pub fn context_description_in_brackets(&self) -> String {
          self.mutation.context_description_in_brackets()
      }
  }
  
  impl AsRef<Mutation> for BakedMutation {
      fn as_ref(&self) -> &Mutation {
          &self.mutation
      }
  }
  
  #[cfg(test)]
  impl Mutation {
      /// Create a new mutation for testing purposes.
      pub fn new_stub() -> Self {
          Self {
              impl_name: None,
              fn_name: None,
              mutator: "stub".to_owned(),
              original_code: "stub".to_owned(),
              mutated_code: "stub".to_owned(),
              source_file: PathBuf::new(),
              location_in_file: "stub".to_owned(),
          }
      }
  }
  