Skip to main content

foundry_common/preprocessor/
mod.rs

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