Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple public methods in scripts as long as there is at least one run method #38

Merged
merged 11 commits into from
Jun 20, 2024
23 changes: 15 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,31 @@ jobs:
profile: minimal
toolchain: nightly
override: true
components: clippy, rustfmt
components: rustfmt

- name: cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all --check

- name: cargo clippy
uses: actions-rs/clippy-check@v1
with:
args: --all --all-features -- -D warnings
token: ${{ secrets.GITHUB_TOKEN }}

- name: cargo doc
uses: actions-rs/cargo@v1
env:
RUSTDOCFLAGS: '-D missing_docs -D rustdoc::missing_doc_code_examples'
RUSTDOCFLAGS: "-D rustdoc::missing_doc_code_examples"
with:
command: doc
args: --workspace --all-features --no-deps --document-private-items

clippy:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@clippy
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- run: cargo clippy --workspace --all-targets --all-features
env:
RUSTFLAGS: -Dwarnings
38 changes: 30 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
license = "MIT"
name = "scopelint"
repository = "https://github.com/ScopeLift/scopelint"
version = "0.0.20"
version = "0.0.21"

[dependencies]
clap = { version = "4.4.2", features = ["derive"] }
Expand All @@ -16,5 +16,5 @@
once_cell = "1.16.0"
regex = "1.6.0"
solang-parser = "0.3.2"
taplo = "0.12.1"
walkdir = "2.3.2"
taplo = "0.13.0"
walkdir = "2.3.2"
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# ScopeLint
# `ScopeLint`

A simple and opinionated tool designed for basic formatting/linting of Solidity and TOML code in foundry projects.

- [Installation](#installation)
- [Usage](#usage)
- [`scopelint fmt`](#scopelint-fmt)
- [`scopelint check`](#scopelint-check)
- [`scopelint spec`](#scopelint-spec)

- [`ScopeLint`](#scopelint)
- [Installation](#installation)
- [Usage](#usage)
- [`scopelint fmt`](#scopelint-fmt)
- [`scopelint check`](#scopelint-check)
- [`scopelint spec`](#scopelint-spec)

## Installation

Expand Down
4 changes: 2 additions & 2 deletions src/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ pub fn parse(file: &Path) -> Result<Parsed, Box<dyn Error>> {
let src = &fs::read_to_string(file)?;

let (pt, comments) = solang_parser::parse(src, 0).map_err(|d| {
eprintln!("{:?}", d);
eprintln!("{d:?}");
"Failed to parse file".to_string()
})?;

Expand Down Expand Up @@ -140,7 +140,7 @@ fn validate(paths: [&str; 3]) -> Result<report::Report, Box<dyn Error>> {
// Run all checks.
results.add_items(validators::test_names::validate(&parsed));
results.add_items(validators::src_names_internal::validate(&parsed));
results.add_items(validators::script_one_pubic_run_method::validate(&parsed));
results.add_items(validators::script_has_public_run_method::validate(&parsed));
results.add_items(validators::constant_names::validate(&parsed));
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/check/validators/constant_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ mod tests {

#[test]
fn test_validate() {
let content = r#"
let content = r"
contract MyContract {
// These have the constant or immutable keyword and should be valid.
uint256 constant MAX_UINT256 = type(uint256).max;
Expand All @@ -90,7 +90,7 @@ mod tests {
address alice = address(123);
uint256 aliceBalance = 500;
}
"#;
";

let expected_findings = ExpectedFindings::new(2);
expected_findings.assert_eq(content, &validate);
Expand Down Expand Up @@ -135,11 +135,11 @@ mod tests {
];

for name in allowed_names {
assert_eq!(is_valid_constant_name(name), true, "{name}");
assert!(is_valid_constant_name(name), "{name}");
}

for name in disallowed_names {
assert_eq!(is_valid_constant_name(name), false, "{name}");
assert!(!is_valid_constant_name(name), "{name}");
}
}
}
2 changes: 1 addition & 1 deletion src/check/validators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub mod formatting;
pub mod constant_names;

/// Validates that a script has a single public method named `run`.
pub mod script_one_pubic_run_method;
pub mod script_has_public_run_method;

/// Validates that internal and private function names are prefixed with an underscore.
pub mod src_names_internal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,27 +50,18 @@ pub fn validate(parsed: &Parsed) -> Vec<InvalidItem> {
"No `run` method found".to_string(),
)]
}
1 => {
if public_methods[0] == "run" {
_ => {
if public_methods.contains(&"run".to_string()) {
Vec::new()
} else {
vec![InvalidItem::new(
ValidatorKind::Script,
parsed,
contract_loc.unwrap(),
"The only public method must be named `run`".to_string(),
contract_loc.unwrap(), //
"No `run` method found".to_string(),
)]
}
}
_ => {
vec![InvalidItem::new(
ValidatorKind::Script,
parsed,
contract_loc.unwrap(),
format!("Scripts must have a single public method named `run` (excluding `setUp`), but the following methods were found: {public_methods:?}"),

)]
}
}
}

Expand All @@ -82,52 +73,59 @@ mod tests {
#[test]
fn test_validate() {
// TODO add another test for the third match arm
let content_good = r#"
let content_good = r"
contract MyContract {
function run() public {}
}
"#;
";

// The number after `bad` on the variable name indicates the match arm covered.
let content_bad0 = r#"
contract MyContract {}
"#;

let content_bad1 = r#"
contract MyContract {
function notRun() public {}
}
"#;

let content_bad2_variant0 = r#"
let content_good_variant0 = r"
contract MyContract {
function run() public {}
function run(string memory config) public {}
}
"#;
";

let content_bad2_variant1 = r#"
let content_good_variant1 = r"
contract MyContract {
function run() public {}
function foo() public {}
}
"#;
";

let content_good_variant2 = r"
contract MyContract {
function run(address admin) public {}
}
";

// The number after `bad` on the variable name indicates the match arm covered.
let content_bad0 = r"
contract MyContract {}
";

let content_bad1 = r"
contract MyContract {
function notRun() public {}
}
";

let content_bad2_variant2 = r#"
let content_bad2_variant0 = r"
contract MyContract {
function foo() public {}
function bar() public {}
}
"#;
";

let expected_findings_good = ExpectedFindings::new(0);
expected_findings_good.assert_eq(content_good, &validate);
expected_findings_good.assert_eq(content_good_variant0, &validate);
expected_findings_good.assert_eq(content_good_variant1, &validate);
expected_findings_good.assert_eq(content_good_variant2, &validate);

let expected_findings_bad = ExpectedFindings { script: 1, ..Default::default() };
expected_findings_bad.assert_eq(content_bad0, &validate);
expected_findings_bad.assert_eq(content_bad1, &validate);
expected_findings_bad.assert_eq(content_bad2_variant0, &validate);
expected_findings_bad.assert_eq(content_bad2_variant1, &validate);
expected_findings_bad.assert_eq(content_bad2_variant2, &validate);
}
}
4 changes: 2 additions & 2 deletions src/check/validators/src_names_internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ mod tests {

#[test]
fn test_validate() {
let content = r#"
let content = r"
contract MyContract {
// Valid names for internal or private src methods.
function _myInternalMethod() internal {}
Expand All @@ -76,7 +76,7 @@ mod tests {
function myPublicMethod() public {}
function myExternalMethod() external {}
}
"#;
";

let expected_findings = ExpectedFindings { src: 2, ..ExpectedFindings::default() };
expected_findings.assert_eq(content, &validate);
Expand Down
8 changes: 4 additions & 4 deletions src/check/validators/test_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ mod tests {

#[test]
fn test_validate() {
let content = r#"
let content = r"
contract MyContract {
// Good test names.
function test_Description() public {}
Expand All @@ -117,7 +117,7 @@ mod tests {
function _testDescription() public {}
function _testDescriptionMoreInfo() public {}
}
"#;
";

let expected_findings = ExpectedFindings { test: 3, ..ExpectedFindings::default() };
expected_findings.assert_eq(content, &validate);
Expand Down Expand Up @@ -172,11 +172,11 @@ mod tests {
];

for name in allowed_names {
assert_eq!(is_valid_test_name(name), true, "{name}");
assert!(is_valid_test_name(name), "{name}");
}

for name in disallowed_names {
assert_eq!(is_valid_test_name(name), false, "{name}");
assert!(!is_valid_test_name(name), "{name}");
}
}
}
3 changes: 1 addition & 2 deletions src/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,6 @@ fn trimmed_fn_name_to_requirement(trimmed_fn_name: &str) -> String {
trimmed_fn_name
.replace('_', ":")
.chars()
.enumerate()
.map(|(_i, c)| if c.is_uppercase() { format!(" {c}") } else { c.to_string() })
.map(|c| if c.is_uppercase() { format!(" {c}") } else { c.to_string() })
.collect::<String>()
}
Loading
Loading