feat: warnings as errors (#1838)

* feat: warnings as errors

* changed the bool arg to Severity and updated its traits

* reformat the test based on the linter

* renamed variable based on property type change and changed a few refs

* updated changelog

* revert changelog iden change

* added test for combining compiler severity filter and ignored error codes and adjusted has error for the added test case

* adjusted has_error to utilize ge functionality in case of info errors

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
Jared Tokuz 2022-11-10 14:19:43 -06:00 committed by GitHub
parent 8de8a29dc2
commit b06452c6ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 122 additions and 13 deletions

View File

@ -1733,8 +1733,9 @@ impl fmt::Display for Error {
} }
} }
#[derive(Clone, Debug, Eq, PartialEq, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
pub enum Severity { pub enum Severity {
#[default]
Error, Error,
Warning, Warning,
Info, Info,

View File

@ -3,7 +3,7 @@
use crate::{ use crate::{
artifacts::{ artifacts::{
contract::{CompactContractBytecode, CompactContractRef, Contract}, contract::{CompactContractBytecode, CompactContractRef, Contract},
Error, Error, Severity,
}, },
buildinfo::RawBuildInfo, buildinfo::RawBuildInfo,
info::ContractInfoRef, info::ContractInfoRef,
@ -31,6 +31,8 @@ pub struct ProjectCompileOutput<T: ArtifactOutput = ConfigurableArtifacts> {
pub(crate) cached_artifacts: Artifacts<T::Artifact>, pub(crate) cached_artifacts: Artifacts<T::Artifact>,
/// errors that should be omitted /// errors that should be omitted
pub(crate) ignored_error_codes: Vec<u64>, pub(crate) ignored_error_codes: Vec<u64>,
/// set minimum level of severity that is treated as an error
pub(crate) compiler_severity_filter: Severity,
} }
impl<T: ArtifactOutput> ProjectCompileOutput<T> { impl<T: ArtifactOutput> ProjectCompileOutput<T> {
@ -197,7 +199,7 @@ impl<T: ArtifactOutput> ProjectCompileOutput<T> {
/// Whether there were errors /// Whether there were errors
pub fn has_compiler_errors(&self) -> bool { pub fn has_compiler_errors(&self) -> bool {
self.compiler_output.has_error() self.compiler_output.has_error(&self.ignored_error_codes, &self.compiler_severity_filter)
} }
/// Whether there were warnings /// Whether there were warnings
@ -398,7 +400,7 @@ impl<T: ArtifactOutput> fmt::Display for ProjectCompileOutput<T> {
if self.compiler_output.is_unchanged() { if self.compiler_output.is_unchanged() {
f.write_str("Nothing to compile") f.write_str("Nothing to compile")
} else { } else {
self.compiler_output.diagnostics(&self.ignored_error_codes).fmt(f) self.compiler_output.diagnostics(&self.ignored_error_codes, self.compiler_severity_filter.clone()).fmt(f)
} }
} }
} }
@ -426,8 +428,16 @@ impl AggregatedCompilerOutput {
} }
/// Whether the output contains a compiler error /// Whether the output contains a compiler error
pub fn has_error(&self) -> bool { pub fn has_error(&self, ignored_error_codes: &[u64], compiler_severity_filter: &Severity) -> bool {
self.errors.iter().any(|err| err.severity.is_error()) 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)
}
return true
}
return false
})
} }
/// Whether the output contains a compiler warning /// Whether the output contains a compiler warning
@ -441,8 +451,8 @@ impl AggregatedCompilerOutput {
}) })
} }
pub fn diagnostics<'a>(&'a self, ignored_error_codes: &'a [u64]) -> OutputDiagnostics { pub fn diagnostics<'a>(&'a self, ignored_error_codes: &'a [u64], compiler_severity_filter: Severity) -> OutputDiagnostics {
OutputDiagnostics { compiler_output: self, ignored_error_codes } OutputDiagnostics { compiler_output: self, ignored_error_codes, compiler_severity_filter }
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
@ -702,12 +712,14 @@ pub struct OutputDiagnostics<'a> {
compiler_output: &'a AggregatedCompilerOutput, compiler_output: &'a AggregatedCompilerOutput,
/// the error codes to ignore /// the error codes to ignore
ignored_error_codes: &'a [u64], ignored_error_codes: &'a [u64],
/// set minimum level of severity that is treated as an error
compiler_severity_filter: Severity,
} }
impl<'a> OutputDiagnostics<'a> { impl<'a> OutputDiagnostics<'a> {
/// Returns true if there is at least one error of high severity /// Returns true if there is at least one error of high severity
pub fn has_error(&self) -> bool { pub fn has_error(&self) -> bool {
self.compiler_output.has_error() self.compiler_output.has_error(&self.ignored_error_codes, &self.compiler_severity_filter)
} }
/// Returns true if there is at least one warning /// Returns true if there is at least one warning

View File

@ -311,7 +311,7 @@ impl<'a, T: ArtifactOutput> CompiledState<'a, T> {
ctx, ctx,
&project.paths, &project.paths,
) )
} else if output.has_error() { } else if output.has_error(&project.ignored_error_codes, &project.compiler_severity_filter) {
trace!("skip writing cache file due to solc errors: {:?}", output.errors); trace!("skip writing cache file due to solc errors: {:?}", output.errors);
project.artifacts_handler().output_to_artifacts( project.artifacts_handler().output_to_artifacts(
&output.contracts, &output.contracts,
@ -358,14 +358,17 @@ impl<'a, T: ArtifactOutput> ArtifactsState<'a, T> {
fn write_cache(self) -> Result<ProjectCompileOutput<T>> { fn write_cache(self) -> Result<ProjectCompileOutput<T>> {
trace!("write cache"); trace!("write cache");
let ArtifactsState { output, cache, compiled_artifacts } = self; let ArtifactsState { output, cache, compiled_artifacts } = self;
let ignored_error_codes = cache.project().ignored_error_codes.clone(); let project = cache.project();
let skip_write_to_disk = cache.project().no_artifacts || output.has_error(); let ignored_error_codes = project.ignored_error_codes.clone();
let compiler_severity_filter = project.compiler_severity_filter.clone();
let skip_write_to_disk = project.no_artifacts || output.has_error(&ignored_error_codes, &compiler_severity_filter);
let cached_artifacts = cache.consume(&compiled_artifacts, !skip_write_to_disk)?; let cached_artifacts = cache.consume(&compiled_artifacts, !skip_write_to_disk)?;
Ok(ProjectCompileOutput { Ok(ProjectCompileOutput {
compiler_output: output, compiler_output: output,
compiled_artifacts, compiled_artifacts,
cached_artifacts, cached_artifacts,
ignored_error_codes, ignored_error_codes,
compiler_severity_filter
}) })
} }
} }

View File

@ -42,7 +42,7 @@ use crate::{
error::{SolcError, SolcIoError}, error::{SolcError, SolcIoError},
sources::{VersionedSourceFile, VersionedSourceFiles}, sources::{VersionedSourceFile, VersionedSourceFiles},
}; };
use artifacts::contract::Contract; use artifacts::{contract::Contract, Severity};
use compile::output::contracts::VersionedContracts; use compile::output::contracts::VersionedContracts;
use error::Result; use error::Result;
use semver::Version; use semver::Version;
@ -73,6 +73,8 @@ pub struct Project<T: ArtifactOutput = ConfigurableArtifacts> {
pub artifacts: T, pub artifacts: T,
/// Errors/Warnings which match these error codes are not going to be logged /// Errors/Warnings which match these error codes are not going to be logged
pub ignored_error_codes: Vec<u64>, pub ignored_error_codes: Vec<u64>,
/// 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 /// The paths which will be allowed for library inclusion
pub allowed_paths: AllowedLibPaths, pub allowed_paths: AllowedLibPaths,
/// The paths which will be used with solc's `--include-path` attribute /// The paths which will be used with solc's `--include-path` attribute
@ -569,6 +571,8 @@ pub struct ProjectBuilder<T: ArtifactOutput = ConfigurableArtifacts> {
artifacts: T, artifacts: T,
/// Which error codes to ignore /// Which error codes to ignore
pub ignored_error_codes: Vec<u64>, pub ignored_error_codes: Vec<u64>,
/// The minimum severity level that is treated as a compiler error
compiler_severity_filter: Severity,
/// All allowed paths for solc's `--allowed-paths` /// All allowed paths for solc's `--allowed-paths`
allowed_paths: AllowedLibPaths, allowed_paths: AllowedLibPaths,
/// Paths to use for solc's `--include-path` /// Paths to use for solc's `--include-path`
@ -591,6 +595,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
slash_paths: true, slash_paths: true,
artifacts, artifacts,
ignored_error_codes: Vec::new(), ignored_error_codes: Vec::new(),
compiler_severity_filter: Severity::Error,
allowed_paths: Default::default(), allowed_paths: Default::default(),
include_paths: Default::default(), include_paths: Default::default(),
solc_jobs: None, solc_jobs: None,
@ -629,6 +634,12 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
self self
} }
#[must_use]
pub fn set_compiler_severity_filter(mut self, compiler_severity_filter: Severity) -> Self {
self.compiler_severity_filter = compiler_severity_filter;
self
}
/// Disables cached builds /// Disables cached builds
#[must_use] #[must_use]
pub fn ephemeral(self) -> Self { pub fn ephemeral(self) -> Self {
@ -727,6 +738,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
no_artifacts, no_artifacts,
auto_detect, auto_detect,
ignored_error_codes, ignored_error_codes,
compiler_severity_filter,
allowed_paths, allowed_paths,
include_paths, include_paths,
solc_jobs, solc_jobs,
@ -746,6 +758,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
slash_paths, slash_paths,
artifacts, artifacts,
ignored_error_codes, ignored_error_codes,
compiler_severity_filter,
allowed_paths, allowed_paths,
include_paths, include_paths,
solc_jobs, solc_jobs,
@ -803,6 +816,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
auto_detect, auto_detect,
artifacts, artifacts,
ignored_error_codes, ignored_error_codes,
compiler_severity_filter,
mut allowed_paths, mut allowed_paths,
include_paths, include_paths,
solc_jobs, solc_jobs,
@ -834,6 +848,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
auto_detect, auto_detect,
artifacts, artifacts,
ignored_error_codes, ignored_error_codes,
compiler_severity_filter,
allowed_paths, allowed_paths,
include_paths, include_paths,
solc_jobs: solc_jobs.unwrap_or_else(num_cpus::get), solc_jobs: solc_jobs.unwrap_or_else(num_cpus::get),

View File

@ -0,0 +1,3 @@
pragma solidity 0.8.6;
contract LicenseWarning {}

View File

@ -1618,6 +1618,81 @@ fn can_compile_model_checker_sample() {
assert!(compiled.has_compiler_warnings()); assert!(compiled.has_compiler_warnings());
} }
#[test]
fn test_compiler_severity_filter() {
fn gen_test_data_warning_path() -> ProjectPathsConfig {
let root =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/test-contract-warnings");
let paths = ProjectPathsConfig::builder().sources(root).build().unwrap();
paths
}
let project = Project::builder()
.no_artifacts()
.paths(gen_test_data_warning_path())
.ephemeral()
.build()
.unwrap();
let compiled = project.compile().unwrap();
assert!(compiled.has_compiler_warnings());
assert!(!compiled.has_compiler_errors());
let project = Project::builder()
.no_artifacts()
.paths(gen_test_data_warning_path())
.ephemeral()
.set_compiler_severity_filter(ethers_solc::artifacts::Severity::Warning)
.build()
.unwrap();
let compiled = project.compile().unwrap();
assert!(compiled.has_compiler_warnings());
assert!(compiled.has_compiler_errors());
}
#[test]
fn test_compiler_severity_filter_and_ignored_error_codes() {
fn gen_test_data_licensing_warning() -> ProjectPathsConfig {
let root =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/test-contract-warnings/LicenseWarning.sol");
let paths = ProjectPathsConfig::builder().sources(root).build().unwrap();
paths
}
let missing_license_error_code = 1878;
let project = Project::builder()
.no_artifacts()
.paths(gen_test_data_licensing_warning())
.ephemeral()
.build()
.unwrap();
let compiled = project.compile().unwrap();
assert!(compiled.has_compiler_warnings());
let project = Project::builder()
.no_artifacts()
.paths(gen_test_data_licensing_warning())
.ephemeral()
.ignore_error_code(missing_license_error_code)
.build()
.unwrap();
let compiled = project.compile().unwrap();
assert!(!compiled.has_compiler_warnings());
assert!(!compiled.has_compiler_errors());
let project = Project::builder()
.no_artifacts()
.paths(gen_test_data_licensing_warning())
.ephemeral()
.ignore_error_code(missing_license_error_code)
.set_compiler_severity_filter(ethers_solc::artifacts::Severity::Warning)
.build()
.unwrap();
let compiled = project.compile().unwrap();
assert!(!compiled.has_compiler_warnings());
assert!(!compiled.has_compiler_errors());
}
fn remove_solc_if_exists(version: &Version) { fn remove_solc_if_exists(version: &Version) {
if Solc::find_svm_installed_version(version.to_string()).unwrap().is_some() { if Solc::find_svm_installed_version(version.to_string()).unwrap().is_some() {
svm::remove_version(version).expect("failed to remove version") svm::remove_version(version).expect("failed to remove version")