foundry_common/preprocessor/
mod.rs1use crate::errors::convert_solar_errors;
2use foundry_compilers::{
3 Compiler, ProjectPathsConfig, SourceParser, apply_updates,
4 artifacts::SolcLanguage,
5 cache::CompilerCache,
6 error::Result,
7 multi::{MultiCompiler, MultiCompilerInput, MultiCompilerLanguage, MultiCompilerSettings},
8 project::Preprocessor,
9 solc::{SolcCompiler, SolcSettings, SolcVersionedInput},
10};
11use solar::parse::{ast::Span, interface::SourceMap};
12use std::{
13 collections::HashSet,
14 ops::{ControlFlow, Range},
15 path::PathBuf,
16};
17
18mod data;
19use data::{collect_preprocessor_data, create_deploy_helpers};
20
21mod deps;
22use deps::{PreprocessorDependencies, remove_bytecode_dependencies};
23
24#[derive(Debug)]
31pub struct DynamicTestLinkingPreprocessor;
32
33impl Preprocessor<SolcCompiler> for DynamicTestLinkingPreprocessor {
34 #[instrument(name = "DynamicTestLinkingPreprocessor::preprocess", skip_all)]
35 fn preprocess(
36 &self,
37 _solc: &SolcCompiler,
38 input: &mut SolcVersionedInput,
39 paths: &ProjectPathsConfig<SolcLanguage>,
40 mocks: &mut HashSet<PathBuf>,
41 ) -> Result<()> {
42 if !input.input.sources.iter().any(|(path, _)| paths.is_test_or_script(path)) {
44 trace!("no tests or scripts to preprocess");
45 return Ok(());
46 }
47
48 let mut compiler =
49 foundry_compilers::resolver::parse::SolParser::new(paths.with_language_ref())
50 .into_compiler();
51 let _ = compiler.enter_mut(|compiler| -> solar::interface::Result {
52 let mut pcx = compiler.parse();
53
54 let mut preprocessed_paths = vec![];
58 let mut script_paths = HashSet::new();
59 let sources = &mut input.input.sources;
60 for (path, source) in sources.iter() {
61 if let Ok(src_file) = compiler
62 .sess()
63 .source_map()
64 .new_source_file(path.clone(), source.content.as_str())
65 && paths.is_test_or_script(path)
66 {
67 pcx.add_file(src_file);
68 if paths.is_script(path) {
69 script_paths.insert(path.clone());
70 }
71 preprocessed_paths.push(path.clone());
72 }
73 }
74
75 pcx.parse();
77 let ControlFlow::Continue(()) = compiler.lower_asts()? else { return Ok(()) };
78 let gcx = compiler.gcx();
79 let mut source_units = sources.keys().cloned().collect::<Vec<_>>();
80 let cache_files = crate::fs::read_to_string(&paths.cache).ok().and_then(|cache| {
83 serde_json::from_str::<CompilerCache<MultiCompilerSettings>>(&cache)
84 .map(|cache| cache.files)
85 .or_else(|_| {
86 serde_json::from_str::<CompilerCache<SolcSettings>>(&cache)
87 .map(|cache| cache.files)
88 })
89 .ok()
90 });
91 if let Some(files) = cache_files {
92 source_units.extend(
93 files
94 .into_keys()
95 .map(|path| path.strip_prefix(&paths.root).unwrap_or(&path).to_path_buf()),
96 );
97 }
98 source_units.sort_unstable();
99 source_units.dedup();
100 let deps = PreprocessorDependencies::new(
105 gcx,
106 &preprocessed_paths,
107 &script_paths,
108 paths,
109 &source_units,
110 mocks,
111 );
112 let data = collect_preprocessor_data(gcx, &deps.referenced_contracts, &paths.root);
114
115 sources.extend(create_deploy_helpers(&data));
117
118 apply_updates(sources, remove_bytecode_dependencies(gcx, &deps, &data));
120
121 Ok(())
122 });
123
124 if let Err(err) = convert_solar_errors(compiler.dcx()) {
126 warn!(%err, "failed preprocessing");
127 }
128
129 Ok(())
130 }
131}
132
133impl Preprocessor<MultiCompiler> for DynamicTestLinkingPreprocessor {
134 fn preprocess(
135 &self,
136 compiler: &MultiCompiler,
137 input: &mut <MultiCompiler as Compiler>::Input,
138 paths: &ProjectPathsConfig<MultiCompilerLanguage>,
139 mocks: &mut HashSet<PathBuf>,
140 ) -> Result<()> {
141 let MultiCompilerInput::Solc(input) = input else { return Ok(()) };
143
144 let Some(solc) = &compiler.solc else { return Ok(()) };
145
146 let paths = paths.clone().with_language::<SolcLanguage>();
147 self.preprocess(solc, input, &paths, mocks)
148 }
149}
150
151#[track_caller]
153fn span_to_range(source_map: &SourceMap, span: Span) -> Range<usize> {
154 source_map.span_to_range(span).unwrap()
155}