Skip to main content

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