foundry_common/preprocessor/
mod.rs

1use foundry_compilers::{
2    apply_updates,
3    artifacts::SolcLanguage,
4    error::Result,
5    multi::{MultiCompiler, MultiCompilerInput, MultiCompilerLanguage},
6    project::Preprocessor,
7    solc::{SolcCompiler, SolcVersionedInput},
8    Compiler, Language, ProjectPathsConfig,
9};
10use solar_parse::{
11    ast::Span,
12    interface::{Session, SourceMap},
13};
14use solar_sema::{thread_local::ThreadLocal, ParsingContext};
15use std::{collections::HashSet, ops::Range, path::PathBuf};
16
17mod data;
18use data::{collect_preprocessor_data, create_deploy_helpers};
19
20mod deps;
21use deps::{remove_bytecode_dependencies, PreprocessorDependencies};
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_source(span).unwrap().1
27}
28
29#[derive(Debug)]
30pub struct TestOptimizerPreprocessor;
31
32impl Preprocessor<SolcCompiler> for TestOptimizerPreprocessor {
33    fn preprocess(
34        &self,
35        _solc: &SolcCompiler,
36        input: &mut SolcVersionedInput,
37        paths: &ProjectPathsConfig<SolcLanguage>,
38        mocks: &mut HashSet<PathBuf>,
39    ) -> Result<()> {
40        // Skip if we are not preprocessing any tests or scripts. Avoids unnecessary AST parsing.
41        if !input.input.sources.iter().any(|(path, _)| paths.is_test_or_script(path)) {
42            trace!("no tests or sources to preprocess");
43            return Ok(());
44        }
45
46        let sess = solar_session_from_solc(input);
47        let _ = sess.enter_parallel(|| -> solar_parse::interface::Result {
48            // Set up the parsing context with the project paths.
49            let mut parsing_context = solar_pcx_from_solc_no_sources(&sess, input, paths);
50
51            // Add the sources into the context.
52            // Include all sources in the source map so as to not re-load them from disk, but only
53            // parse and preprocess tests and scripts.
54            let mut preprocessed_paths = vec![];
55            let sources = &mut input.input.sources;
56            for (path, source) in sources.iter() {
57                if let Ok(src_file) =
58                    sess.source_map().new_source_file(path.clone(), source.content.as_str())
59                {
60                    if paths.is_test_or_script(path) {
61                        parsing_context.add_file(src_file);
62                        preprocessed_paths.push(path.clone());
63                    }
64                }
65            }
66
67            // Parse and preprocess.
68            let hir_arena = ThreadLocal::new();
69            if let Some(gcx) = parsing_context.parse_and_lower(&hir_arena)? {
70                let hir = &gcx.get().hir;
71                // Collect tests and scripts dependencies and identify mock contracts.
72                let deps = PreprocessorDependencies::new(
73                    &sess,
74                    hir,
75                    &preprocessed_paths,
76                    &paths.paths_relative().sources,
77                    &paths.root,
78                    mocks,
79                );
80                // Collect data of source contracts referenced in tests and scripts.
81                let data = collect_preprocessor_data(&sess, hir, &deps.referenced_contracts);
82
83                // Extend existing sources with preprocessor deploy helper sources.
84                sources.extend(create_deploy_helpers(&data));
85
86                // Generate and apply preprocessor source updates.
87                apply_updates(sources, remove_bytecode_dependencies(hir, &deps, &data));
88            }
89
90            Ok(())
91        });
92
93        // Warn if any diagnostics emitted during content parsing.
94        if let Err(err) = sess.emitted_errors().unwrap() {
95            warn!("failed preprocessing {err}");
96        }
97
98        Ok(())
99    }
100}
101
102impl Preprocessor<MultiCompiler> for TestOptimizerPreprocessor {
103    fn preprocess(
104        &self,
105        compiler: &MultiCompiler,
106        input: &mut <MultiCompiler as Compiler>::Input,
107        paths: &ProjectPathsConfig<MultiCompilerLanguage>,
108        mocks: &mut HashSet<PathBuf>,
109    ) -> Result<()> {
110        // Preprocess only Solc compilers.
111        let MultiCompilerInput::Solc(input) = input else { return Ok(()) };
112
113        let Some(solc) = &compiler.solc else { return Ok(()) };
114
115        let paths = paths.clone().with_language::<SolcLanguage>();
116        self.preprocess(solc, input, &paths, mocks)
117    }
118}
119
120fn solar_session_from_solc(solc: &SolcVersionedInput) -> Session {
121    use solar_parse::interface::config;
122
123    Session::builder()
124        .with_buffer_emitter(Default::default())
125        .opts(config::Opts {
126            language: match solc.input.language {
127                SolcLanguage::Solidity => config::Language::Solidity,
128                SolcLanguage::Yul => config::Language::Yul,
129                _ => unimplemented!(),
130            },
131
132            // TODO: ...
133            /*
134            evm_version: solc.input.settings.evm_version,
135            */
136            ..Default::default()
137        })
138        .build()
139}
140
141fn solar_pcx_from_solc_no_sources<'sess>(
142    sess: &'sess Session,
143    solc: &SolcVersionedInput,
144    paths: &ProjectPathsConfig<impl Language>,
145) -> ParsingContext<'sess> {
146    let mut pcx = ParsingContext::new(sess);
147    pcx.file_resolver.set_current_dir(solc.cli_settings.base_path.as_ref().unwrap_or(&paths.root));
148    for remapping in &paths.remappings {
149        pcx.file_resolver.add_import_remapping(solar_sema::interface::config::ImportRemapping {
150            context: remapping.context.clone().unwrap_or_default(),
151            prefix: remapping.name.clone(),
152            path: remapping.path.clone(),
153        });
154    }
155    pcx.file_resolver.add_include_paths(solc.cli_settings.include_paths.iter().cloned());
156    pcx
157}