necessist_backends/
lib.rs

1use anyhow::Result;
2use clap::ValueEnum;
3use heck::ToKebabCase;
4use necessist_core::{
5    __Rewriter as Rewriter, LightContext, SourceFile, Span,
6    framework::{
7        Applicable, AsParse, AsRun, Interface, Parse as ParseHigh, Postprocess, Run as RunHigh,
8        ToImplementation,
9    },
10};
11use std::{cell::RefCell, path::Path, rc::Rc};
12use strum_macros::EnumIter;
13use subprocess::Exec;
14
15// Framework modules
16
17mod anchor;
18use anchor::Anchor;
19
20mod foundry;
21use foundry::Foundry;
22
23mod go;
24use go::Go;
25
26mod hardhat;
27use hardhat::Hardhat;
28
29mod rust;
30use rust::Rust;
31
32mod vitest;
33use vitest::Vitest;
34
35// Other modules
36
37mod parsing;
38use parsing::{AbstractTypes, MaybeNamed, Named, ParseAdapter, ParseLow, Spanned, WalkDirResult};
39
40mod generic_visitor;
41use generic_visitor::GenericVisitor;
42
43mod running;
44use running::{ProcessLines, RunAdapter, RunLow};
45
46mod ts;
47
48mod utils;
49use utils::{OutputAccessors, OutputStrippedOfAnsiScapes};
50
51#[derive(Debug, Clone, Copy, EnumIter, Eq, Ord, PartialEq, PartialOrd, ValueEnum)]
52#[non_exhaustive]
53#[remain::sorted]
54pub enum Identifier {
55    #[value(alias("anchor-ts"))]
56    Anchor,
57    Foundry,
58    Go,
59    #[value(alias("hardhat-ts"))]
60    Hardhat,
61    Rust,
62    Vitest,
63}
64
65impl Applicable for Identifier {
66    fn applicable(&self, context: &LightContext) -> Result<bool> {
67        match *self {
68            Self::Anchor => Anchor::applicable(context),
69            Self::Foundry => Foundry::applicable(context),
70            Self::Go => Go::applicable(context),
71            Self::Hardhat => Hardhat::applicable(context),
72            Self::Rust => Rust::applicable(context),
73            Self::Vitest => Vitest::applicable(context),
74        }
75    }
76}
77
78impl ToImplementation for Identifier {
79    // smoelius: `Anchor`, `Hardhat`, and `Vitest` implement the `ParseLow` interface indirectly
80    // through `ts::Mocha`. They implement the high-level `Run` interface directly.
81    fn to_implementation(&self, context: &LightContext) -> Result<Option<Box<dyn Interface>>> {
82        match *self {
83            Self::Anchor => {
84                let anchor = Anchor::new(context)?;
85                Ok(Some(Box::new(anchor)))
86            }
87
88            Self::Foundry => Ok(Some(implementation_as_interface(ParseRunAdapter::new)(
89                Foundry::new(),
90            ))),
91
92            Self::Go => Ok(Some(implementation_as_interface(ParseRunAdapter::new)(
93                Go::new(),
94            ))),
95
96            Self::Hardhat => Ok(Some(Box::new(Hardhat::new()))),
97
98            Self::Rust => Ok(Some(implementation_as_interface(ParseRunAdapter::new)(
99                Rust::new(),
100            ))),
101
102            Self::Vitest => Ok(Some(Box::new(Vitest::new()))),
103        }
104    }
105}
106
107impl std::fmt::Display for Identifier {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        write!(f, "{}", format!("{self:?}").to_kebab_case())
110    }
111}
112
113/// Utility function
114fn implementation_as_interface<T, U: Interface + 'static>(
115    adapter: impl Fn(T) -> U,
116) -> impl Fn(T) -> Box<dyn Interface> {
117    move |implementation| Box::new(adapter(implementation)) as Box<dyn Interface>
118}
119
120impl<T: RunHigh> RunHigh for ParseAdapter<T> {
121    fn dry_run(&self, context: &LightContext, source_file: &Path) -> Result<()> {
122        self.0.dry_run(context, source_file)
123    }
124    fn instrument_source_file(
125        &self,
126        context: &LightContext,
127        rewriter: &mut Rewriter,
128        source_file: &SourceFile,
129        n_instrumentable_statements: usize,
130    ) -> Result<()> {
131        self.0
132            .instrument_source_file(context, rewriter, source_file, n_instrumentable_statements)
133    }
134    fn statement_prefix_and_suffix(&self, span: &Span) -> Result<(String, String)> {
135        self.0.statement_prefix_and_suffix(span)
136    }
137    fn build_source_file(&self, context: &LightContext, source_file: &Path) -> Result<()> {
138        self.0.build_source_file(context, source_file)
139    }
140    fn exec(
141        &self,
142        context: &LightContext,
143        test_name: &str,
144        span: &Span,
145    ) -> Result<Option<(Exec, Option<Box<Postprocess>>)>> {
146        self.0.exec(context, test_name, span)
147    }
148}
149
150impl<T: ParseLow + RunHigh> Interface for ParseAdapter<T> {}
151
152struct ParseRunAdapter<T> {
153    parse: ParseAdapter<Rc<RefCell<T>>>,
154    run: RunAdapter<Rc<RefCell<T>>>,
155}
156
157impl<T> ParseRunAdapter<T> {
158    fn new(value: T) -> Self {
159        let rc = Rc::new(RefCell::new(value));
160        Self {
161            parse: ParseAdapter(rc.clone()),
162            run: RunAdapter(rc),
163        }
164    }
165}
166
167impl<T: ParseLow> AsParse for ParseRunAdapter<T> {
168    fn as_parse(&self) -> &dyn ParseHigh {
169        &self.parse
170    }
171    fn as_parse_mut(&mut self) -> &mut dyn ParseHigh {
172        &mut self.parse
173    }
174}
175
176impl<T: RunLow> AsRun for ParseRunAdapter<T> {
177    fn as_run(&self) -> &dyn RunHigh {
178        &self.run
179    }
180}
181
182impl<T: ParseLow + RunLow> Interface for ParseRunAdapter<T> {}