foundry_common/preprocessor/
mod.rs

1use foundry_compilers::{
2    Compiler, ProjectPathsConfig, SourceParser, apply_updates,
3    artifacts::SolcLanguage,
4    error::Result,
5    multi::{MultiCompiler, MultiCompilerInput, MultiCompilerLanguage},
6    project::Preprocessor,
7    solc::{SolcCompiler, SolcVersionedInput},
8};
9use solar::parse::{ast::Span, interface::SourceMap};
10use std::{
11    collections::HashSet,
12    ops::{ControlFlow, Range},
13    path::PathBuf,
14};
15
16mod data;
17use data::{collect_preprocessor_data, create_deploy_helpers};
18
19mod deps;
20use deps::{PreprocessorDependencies, remove_bytecode_dependencies};
21
22/// Returns the range of the given span in the source map.
23#[track_caller]
24fn span_to_range(source_map: &SourceMap, span: Span) -> Range<usize> {
25    source_map.span_to_source(span).unwrap().1
26}
27
28/// Preprocessor that replaces static bytecode linking in tests and scripts (`new Contract`) with
29/// dynamic linkage through (`Vm.create*`).
30///
31/// This allows for more efficient caching when iterating on tests.
32///
33/// See <https://github.com/foundry-rs/foundry/pull/10010>.
34#[derive(Debug)]
35pub struct DynamicTestLinkingPreprocessor;
36
37impl Preprocessor<SolcCompiler> for DynamicTestLinkingPreprocessor {
38    #[instrument(name = "DynamicTestLinkingPreprocessor::preprocess", skip_all)]
39    fn preprocess(
40        &self,
41        _solc: &SolcCompiler,
42        input: &mut SolcVersionedInput,
43        paths: &ProjectPathsConfig<SolcLanguage>,
44        mocks: &mut HashSet<PathBuf>,
45    ) -> Result<()> {
46        // Skip if we are not preprocessing any tests or scripts. Avoids unnecessary AST parsing.
47        if !input.input.sources.iter().any(|(path, _)| paths.is_test_or_script(path)) {
48            trace!("no tests or sources to preprocess");
49            return Ok(());
50        }
51
52        let mut compiler =
53            foundry_compilers::resolver::parse::SolParser::new(paths.with_language_ref())
54                .into_compiler();
55        let _ = compiler.enter_mut(|compiler| -> solar::parse::interface::Result {
56            let mut pcx = compiler.parse();
57
58            // Add the sources into the context.
59            // Include all sources in the source map so as to not re-load them from disk, but only
60            // parse and preprocess tests and scripts.
61            let mut preprocessed_paths = vec![];
62            let sources = &mut input.input.sources;
63            for (path, source) in sources.iter() {
64                if let Ok(src_file) = compiler
65                    .sess()
66                    .source_map()
67                    .new_source_file(path.clone(), source.content.as_str())
68                    && paths.is_test_or_script(path)
69                {
70                    pcx.add_file(src_file);
71                    preprocessed_paths.push(path.clone());
72                }
73            }
74
75            // Parse and preprocess.
76            pcx.parse();
77            let ControlFlow::Continue(()) = compiler.lower_asts()? else { return Ok(()) };
78            let gcx = compiler.gcx();
79            // Collect tests and scripts dependencies and identify mock contracts.
80            let deps = PreprocessorDependencies::new(
81                gcx,
82                &preprocessed_paths,
83                &paths.paths_relative().sources,
84                &paths.root,
85                mocks,
86            );
87            // Collect data of source contracts referenced in tests and scripts.
88            let data = collect_preprocessor_data(gcx, &deps.referenced_contracts);
89
90            // Extend existing sources with preprocessor deploy helper sources.
91            sources.extend(create_deploy_helpers(&data));
92
93            // Generate and apply preprocessor source updates.
94            apply_updates(sources, remove_bytecode_dependencies(gcx, &deps, &data));
95
96            Ok(())
97        });
98
99        // Warn if any diagnostics emitted during content parsing.
100        if let Err(err) = compiler.sess().emitted_errors().unwrap() {
101            warn!("failed preprocessing:\n{err}");
102        }
103
104        Ok(())
105    }
106}
107
108impl Preprocessor<MultiCompiler> for DynamicTestLinkingPreprocessor {
109    fn preprocess(
110        &self,
111        compiler: &MultiCompiler,
112        input: &mut <MultiCompiler as Compiler>::Input,
113        paths: &ProjectPathsConfig<MultiCompilerLanguage>,
114        mocks: &mut HashSet<PathBuf>,
115    ) -> Result<()> {
116        // Preprocess only Solc compilers.
117        let MultiCompilerInput::Solc(input) = input else { return Ok(()) };
118
119        let Some(solc) = &compiler.solc else { return Ok(()) };
120
121        let paths = paths.clone().with_language::<SolcLanguage>();
122        self.preprocess(solc, input, &paths, mocks)
123    }
124}