Skip to content

Commit

Permalink
Option to ignore warnings from dependencies in foundry.toml (#69)
Browse files Browse the repository at this point in the history
[Related to #1200](foundry-rs/foundry#1200)

---------

Co-authored-by: Matthias Seitz <[email protected]>
  • Loading branch information
loocapro and mattsse authored Feb 9, 2024
1 parent 564eb85 commit c42032f
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 59 deletions.
29 changes: 21 additions & 8 deletions src/artifacts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
#![allow(ambiguous_glob_reexports)]

use crate::{
compile::*, error::SolcIoError, remappings::Remapping, utils, ProjectPathsConfig, SolcError,
compile::*, error::SolcIoError, output::ErrorFilter, remappings::Remapping, utils,
ProjectPathsConfig, SolcError,
};
use alloy_primitives::hex;
use md5::Digest;
Expand Down Expand Up @@ -1582,14 +1583,26 @@ impl CompilerOutput {
self.errors.iter().any(|err| err.severity.is_error())
}

/// Whether the output contains a compiler warning
pub fn has_warning(&self, ignored_error_codes: &[u64]) -> bool {
self.errors.iter().any(|err| {
if err.severity.is_warning() {
err.error_code.as_ref().map_or(false, |code| !ignored_error_codes.contains(code))
} else {
false
/// Checks if there are any compiler warnings that are not ignored by the specified error codes
/// and file paths.
pub fn has_warning<'a>(&self, filter: impl Into<ErrorFilter<'a>>) -> bool {
let filter: ErrorFilter<'_> = filter.into();
self.errors.iter().any(|error| {
if !error.severity.is_warning() {
return false;
}

let is_code_ignored = filter.is_code_ignored(error.error_code);

let is_file_ignored = error
.source_location
.as_ref()
.map_or(false, |location| filter.is_file_ignored(Path::new(&location.file)));

// Only consider warnings that are not ignored by either code or file path.
// Hence, return `true` for warnings that are not ignored, making the function
// return `true` if any such warnings exist.
!(is_code_ignored || is_file_ignored)
})
}

Expand Down
121 changes: 106 additions & 15 deletions src/compile/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ use crate::{
use contracts::{VersionedContract, VersionedContracts};
use semver::Version;
use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, fmt, path::Path};
use std::{
borrow::Cow,
collections::BTreeMap,
fmt,
path::{Path, PathBuf},
};
use yansi::Paint;

pub mod contracts;
Expand All @@ -32,6 +37,8 @@ pub struct ProjectCompileOutput<T: ArtifactOutput = ConfigurableArtifacts> {
pub(crate) cached_artifacts: Artifacts<T::Artifact>,
/// errors that should be omitted
pub(crate) ignored_error_codes: Vec<u64>,
/// paths that should be omitted
pub(crate) ignored_file_paths: Vec<PathBuf>,
/// set minimum level of severity that is treated as an error
pub(crate) compiler_severity_filter: Severity,
}
Expand Down Expand Up @@ -245,12 +252,17 @@ impl<T: ArtifactOutput> ProjectCompileOutput<T> {

/// Returns whether any errors were emitted by the compiler.
pub fn has_compiler_errors(&self) -> bool {
self.compiler_output.has_error(&self.ignored_error_codes, &self.compiler_severity_filter)
self.compiler_output.has_error(
&self.ignored_error_codes,
&self.ignored_file_paths,
&self.compiler_severity_filter,
)
}

/// Returns whether any warnings were emitted by the compiler.
pub fn has_compiler_warnings(&self) -> bool {
self.compiler_output.has_warning(&self.ignored_error_codes)
let filter = ErrorFilter::new(&self.ignored_error_codes, &self.ignored_file_paths);
self.compiler_output.has_warning(filter)
}

/// Panics if any errors were emitted by the compiler.
Expand Down Expand Up @@ -463,7 +475,11 @@ impl<T: ArtifactOutput> fmt::Display for ProjectCompileOutput<T> {
f.write_str("Nothing to compile")
} else {
self.compiler_output
.diagnostics(&self.ignored_error_codes, self.compiler_severity_filter)
.diagnostics(
&self.ignored_error_codes,
&self.ignored_file_paths,
self.compiler_severity_filter,
)
.fmt(f)
}
}
Expand All @@ -484,6 +500,46 @@ pub struct AggregatedCompilerOutput {
pub build_infos: BTreeMap<Version, RawBuildInfo>,
}

/// How to filter errors/warnings
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ErrorFilter<'a> {
/// Ignore errors/warnings with these codes
pub error_codes: Cow<'a, [u64]>,
/// Ignore errors/warnings from these file paths
pub ignored_file_paths: Cow<'a, [PathBuf]>,
}

impl<'a> ErrorFilter<'a> {
/// Creates a new `ErrorFilter` with the given error codes and ignored file paths
pub fn new(error_codes: &'a [u64], ignored_file_paths: &'a [PathBuf]) -> Self {
ErrorFilter {
error_codes: Cow::Borrowed(error_codes),
ignored_file_paths: Cow::Borrowed(ignored_file_paths),
}
}
/// Helper function to check if an error code is ignored
pub fn is_code_ignored(&self, code: Option<u64>) -> bool {
match code {
Some(code) => self.error_codes.contains(&code),
None => false,
}
}

/// Helper function to check if an error's file path is ignored
pub fn is_file_ignored(&self, file_path: &Path) -> bool {
self.ignored_file_paths.iter().any(|ignored_path| file_path.starts_with(ignored_path))
}
}

impl<'a> From<&'a [u64]> for ErrorFilter<'a> {
fn from(error_codes: &'a [u64]) -> Self {
ErrorFilter {
error_codes: Cow::Borrowed(error_codes),
ignored_file_paths: Cow::Borrowed(&[]),
}
}
}

impl AggregatedCompilerOutput {
/// Converts all `\\` separators in _all_ paths to `/`
pub fn slash_paths(&mut self) {
Expand All @@ -495,36 +551,57 @@ impl AggregatedCompilerOutput {
pub fn has_error(
&self,
ignored_error_codes: &[u64],
ignored_file_paths: &[PathBuf],
compiler_severity_filter: &Severity,
) -> bool {
self.errors.iter().any(|err| {
if compiler_severity_filter.ge(&err.severity) {
if compiler_severity_filter.is_warning() {
return self.has_warning(ignored_error_codes);
// skip ignored error codes and file path from warnings
let filter = ErrorFilter::new(ignored_error_codes, ignored_file_paths);
return self.has_warning(filter);
}
return true;
}
false
})
}

/// Whether the output contains a compiler warning
pub fn has_warning(&self, ignored_error_codes: &[u64]) -> bool {
self.errors.iter().any(|err| {
if err.severity.is_warning() {
err.error_code.as_ref().map_or(false, |code| !ignored_error_codes.contains(code))
} else {
false
/// Checks if there are any compiler warnings that are not ignored by the specified error codes
/// and file paths.
pub fn has_warning<'a>(&self, filter: impl Into<ErrorFilter<'a>>) -> bool {
let filter: ErrorFilter<'_> = filter.into();
self.errors.iter().any(|error| {
if !error.severity.is_warning() {
return false;
}

let is_code_ignored = filter.is_code_ignored(error.error_code);

let is_file_ignored = error
.source_location
.as_ref()
.map_or(false, |location| filter.is_file_ignored(Path::new(&location.file)));

// Only consider warnings that are not ignored by either code or file path.
// Hence, return `true` for warnings that are not ignored, making the function
// return `true` if any such warnings exist.
!(is_code_ignored || is_file_ignored)
})
}

pub fn diagnostics<'a>(
&'a self,
ignored_error_codes: &'a [u64],
ignored_file_paths: &'a [PathBuf],
compiler_severity_filter: Severity,
) -> OutputDiagnostics<'a> {
OutputDiagnostics { compiler_output: self, ignored_error_codes, compiler_severity_filter }
OutputDiagnostics {
compiler_output: self,
ignored_error_codes,
ignored_file_paths,
compiler_severity_filter,
}
}

pub fn is_empty(&self) -> bool {
Expand Down Expand Up @@ -784,19 +861,26 @@ pub struct OutputDiagnostics<'a> {
compiler_output: &'a AggregatedCompilerOutput,
/// the error codes to ignore
ignored_error_codes: &'a [u64],
/// the file paths to ignore
ignored_file_paths: &'a [PathBuf],
/// set minimum level of severity that is treated as an error
compiler_severity_filter: Severity,
}

impl<'a> OutputDiagnostics<'a> {
/// Returns true if there is at least one error of high severity
pub fn has_error(&self) -> bool {
self.compiler_output.has_error(self.ignored_error_codes, &self.compiler_severity_filter)
self.compiler_output.has_error(
self.ignored_error_codes,
self.ignored_file_paths,
&self.compiler_severity_filter,
)
}

/// Returns true if there is at least one warning
pub fn has_warning(&self) -> bool {
self.compiler_output.has_warning(self.ignored_error_codes)
let filter = ErrorFilter::new(self.ignored_error_codes, self.ignored_file_paths);
self.compiler_output.has_warning(filter)
}

/// Returns true if the contract is a expected to be a test
Expand Down Expand Up @@ -833,6 +917,13 @@ impl<'a> fmt::Display for OutputDiagnostics<'a> {
// from a test file we skip
ignored =
self.is_test(&source_location.file) && (code == 1878 || code == 5574);

// we ignore warnings coming from ignored files
let source_path = Path::new(&source_location.file);
ignored |= self
.ignored_file_paths
.iter()
.any(|ignored_path| source_path.starts_with(ignored_path));
}

ignored |= self.ignored_error_codes.contains(&code);
Expand Down
12 changes: 9 additions & 3 deletions src/compile/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,11 @@ impl<'a, T: ArtifactOutput> CompiledState<'a, T> {
ctx,
&project.paths,
)
} else if output.has_error(&project.ignored_error_codes, &project.compiler_severity_filter)
{
} else if output.has_error(
&project.ignored_error_codes,
&project.ignored_file_paths,
&project.compiler_severity_filter,
) {
trace!("skip writing cache file due to solc errors: {:?}", output.errors);
project.artifacts_handler().output_to_artifacts(
&output.contracts,
Expand Down Expand Up @@ -352,8 +355,10 @@ impl<'a, T: ArtifactOutput> ArtifactsState<'a, T> {
let ArtifactsState { output, cache, compiled_artifacts } = self;
let project = cache.project();
let ignored_error_codes = project.ignored_error_codes.clone();
let ignored_file_paths = project.ignored_file_paths.clone();
let compiler_severity_filter = project.compiler_severity_filter;
let has_error = output.has_error(&ignored_error_codes, &compiler_severity_filter);
let has_error =
output.has_error(&ignored_error_codes, &ignored_file_paths, &compiler_severity_filter);
let skip_write_to_disk = project.no_artifacts || has_error;
trace!(has_error, project.no_artifacts, skip_write_to_disk, cache_path=?project.cache_path(),"prepare writing cache file");

Expand All @@ -363,6 +368,7 @@ impl<'a, T: ArtifactOutput> ArtifactsState<'a, T> {
compiled_artifacts,
cached_artifacts,
ignored_error_codes,
ignored_file_paths,
compiler_severity_filter,
})
}
Expand Down
14 changes: 14 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ pub struct Project<T: ArtifactOutput = ConfigurableArtifacts> {
pub artifacts: T,
/// Errors/Warnings which match these error codes are not going to be logged
pub ignored_error_codes: Vec<u64>,
/// Errors/Warnings which match these file paths are not going to be logged
pub ignored_file_paths: Vec<PathBuf>,
/// The minimum severity level that is treated as a compiler error
pub compiler_severity_filter: Severity,
/// The paths which will be allowed for library inclusion
Expand Down Expand Up @@ -575,6 +577,8 @@ pub struct ProjectBuilder<T: ArtifactOutput = ConfigurableArtifacts> {
artifacts: T,
/// Which error codes to ignore
pub ignored_error_codes: Vec<u64>,
/// Which file paths to ignore
pub ignored_file_paths: Vec<PathBuf>,
/// The minimum severity level that is treated as a compiler error
compiler_severity_filter: Severity,
/// All allowed paths for solc's `--allowed-paths`
Expand All @@ -599,6 +603,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
slash_paths: true,
artifacts,
ignored_error_codes: Vec::new(),
ignored_file_paths: Vec::new(),
compiler_severity_filter: Severity::Error,
allowed_paths: Default::default(),
include_paths: Default::default(),
Expand Down Expand Up @@ -638,6 +643,11 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
self
}

pub fn ignore_paths(mut self, paths: Vec<PathBuf>) -> Self {
self.ignored_file_paths = paths;
self
}

#[must_use]
pub fn set_compiler_severity_filter(mut self, compiler_severity_filter: Severity) -> Self {
self.compiler_severity_filter = compiler_severity_filter;
Expand Down Expand Up @@ -749,6 +759,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
offline,
build_info,
slash_paths,
ignored_file_paths,
..
} = self;
ProjectBuilder {
Expand All @@ -762,6 +773,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
slash_paths,
artifacts,
ignored_error_codes,
ignored_file_paths,
compiler_severity_filter,
allowed_paths,
include_paths,
Expand Down Expand Up @@ -820,6 +832,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
auto_detect,
artifacts,
ignored_error_codes,
ignored_file_paths,
compiler_severity_filter,
mut allowed_paths,
include_paths,
Expand Down Expand Up @@ -852,6 +865,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
auto_detect,
artifacts,
ignored_error_codes,
ignored_file_paths,
compiler_severity_filter,
allowed_paths,
include_paths,
Expand Down
6 changes: 3 additions & 3 deletions src/project_util/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ trait NamingStrategy {
fn new_source_file_name(&mut self, id: usize) -> String;

/// Return a new name for the given source file id
#[allow(unused)]
fn new_lib_file_name(&mut self, id: usize) -> String;

/// Return a new name for the given lib id
Expand All @@ -388,9 +389,8 @@ trait NamingStrategy {

/// A primitive naming that simply uses ids to create unique names
#[derive(Debug, Clone, Copy, Default)]
pub struct SimpleNamingStrategy {
_priv: (),
}
#[non_exhaustive]
pub struct SimpleNamingStrategy;

impl NamingStrategy for SimpleNamingStrategy {
fn new_source_file_name(&mut self, id: usize) -> String {
Expand Down
8 changes: 8 additions & 0 deletions src/project_util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,14 @@ impl TempProject<ConfigurableArtifacts> {
Ok(Self::create_new(tmp_dir, inner)?)
}

pub fn dapptools_with_ignore_paths(paths_to_ignore: Vec<PathBuf>) -> Result<Self> {
let tmp_dir = tempdir("tmp_dapp")?;
let paths = ProjectPathsConfig::dapptools(tmp_dir.path())?;

let inner = Project::builder().paths(paths).ignore_paths(paths_to_ignore).build()?;
Ok(Self::create_new(tmp_dir, inner)?)
}

/// Creates an initialized dapptools style workspace in a new temporary dir
pub fn dapptools_init() -> Result<Self> {
let mut project = Self::dapptools()?;
Expand Down
Loading

0 comments on commit c42032f

Please sign in to comment.