mod cloud_print;
mod colored_print;
mod file_name_printer;
mod interactive_print;
mod json_print;
use crate::lang::SgLang;
use ast_grep_config::{Fixer, RuleConfig};
use ast_grep_core::{tree_sitter::StrDoc, Matcher, NodeMatch as SgNodeMatch};
use anyhow::Result;
use clap::ValueEnum;
use std::borrow::Cow;
use std::path::Path;
pub use cloud_print::{CloudPrinter, Platform};
pub use codespan_reporting::files::SimpleFile;
use codespan_reporting::term::termcolor::ColorChoice;
use colored_print::PrintStyles;
pub use colored_print::{ColoredPrinter, Heading, ReportStyle};
pub use file_name_printer::FileNamePrinter;
pub use interactive_print::InteractivePrinter;
pub use json_print::{JSONPrinter, JsonStyle};
type NodeMatch<'a> = SgNodeMatch<'a, StrDoc<SgLang>>;
pub trait PrintProcessor<Output>: Send + Sync + 'static {
fn print_rule(
&self,
matches: Vec<NodeMatch>,
file: SimpleFile<Cow<str>, &str>,
rule: &RuleConfig<SgLang>,
) -> Result<Output>;
fn print_matches(&self, matches: Vec<NodeMatch>, path: &Path) -> Result<Output>;
fn print_diffs(&self, diffs: Vec<Diff>, path: &Path) -> Result<Output>;
fn print_rule_diffs(
&self,
diffs: Vec<(Diff, &RuleConfig<SgLang>)>,
path: &Path,
) -> Result<Output>;
}
pub trait Printer {
type Processed: Send + 'static;
type Processor: PrintProcessor<Self::Processed>;
fn get_processor(&self) -> Self::Processor;
fn process(&mut self, processed: Self::Processed) -> Result<()>;
#[inline]
fn before_print(&mut self) -> Result<()> {
Ok(())
}
#[inline]
fn after_print(&mut self) -> Result<()> {
Ok(())
}
}
#[derive(Clone)]
pub struct AdditionalFix {
pub replacement: String,
pub range: std::ops::Range<usize>,
pub title: Option<String>,
}
#[derive(Clone)]
pub struct Diff<'n> {
pub node_match: NodeMatch<'n>,
pub replacement: String,
pub range: std::ops::Range<usize>,
pub title: Option<String>,
pub additional_fixes: Option<Box<[AdditionalFix]>>,
}
impl<'n> Diff<'n> {
pub fn generate(node_match: NodeMatch<'n>, matcher: &impl Matcher, rewrite: &Fixer) -> Self {
let edit = node_match.make_edit(matcher, rewrite);
let replacement = String::from_utf8(edit.inserted_text).unwrap();
Self {
node_match,
replacement,
range: edit.position..edit.position + edit.deleted_length,
additional_fixes: None,
title: rewrite.title().map(|t| t.to_string()),
}
}
pub fn multiple(
node_match: NodeMatch<'n>,
matcher: &impl Matcher,
fixers: &[Fixer],
) -> Option<Self> {
let fixer = fixers.first()?;
let mut ret = Self::generate(node_match.clone(), matcher, fixer);
if fixers.len() == 1 {
return Some(ret);
}
let additional = fixers
.iter()
.skip(1)
.map(|f| {
let edit = node_match.make_edit(matcher, f);
AdditionalFix {
replacement: String::from_utf8(edit.inserted_text).unwrap(),
range: edit.position..edit.position + edit.deleted_length,
title: f.title().map(|t| t.to_string()),
}
})
.collect::<Vec<_>>()
.into_boxed_slice();
ret.additional_fixes = Some(additional);
Some(ret)
}
pub fn into_list(mut self) -> Vec<Self> {
let node_match = self.node_match.clone();
let additional_fixes = self.additional_fixes.take();
let mut ret = vec![self];
ret.extend(additional_fixes.into_iter().flatten().map(|f| Self {
node_match: node_match.clone(),
replacement: f.replacement,
range: f.range,
additional_fixes: None,
title: f.title,
}));
ret
}
pub fn get_root_text(&self) -> &'n str {
self.node_match.root().get_text()
}
}
#[derive(ValueEnum, Clone, Copy)]
pub enum ColorArg {
Auto,
Always,
Ansi,
Never,
}
impl ColorArg {
pub fn should_use_color(self) -> bool {
use colored_print::should_use_color;
should_use_color(&self.into())
}
}
impl From<ColorArg> for ColorChoice {
fn from(arg: ColorArg) -> ColorChoice {
use ColorArg::*;
match arg {
Auto => {
if atty::is(atty::Stream::Stdout) {
ColorChoice::Auto
} else {
ColorChoice::Never
}
}
Always => ColorChoice::Always,
Ansi => ColorChoice::AlwaysAnsi,
Never => ColorChoice::Never,
}
}
}