Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ fn validate(paths: [&str; 3]) -> Result<report::Report, Box<dyn Error>> {
results.add_items(validators::src_names_internal::validate(&parsed));
results.add_items(validators::script_has_public_run_method::validate(&parsed));
results.add_items(validators::constant_names::validate(&parsed));
results.add_items(validators::src_spdx_header::validate(&parsed));
}
}
Ok(results)
Expand Down
3 changes: 3 additions & 0 deletions src/check/validators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ pub mod src_names_internal;

/// Validates that test names are in the correct format.
pub mod test_names;

/// Validates that source files have SPDX license headers.
pub mod src_spdx_header;
147 changes: 147 additions & 0 deletions src/check/validators/src_spdx_header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use crate::check::{
utils::{FileKind, InvalidItem, IsFileKind, ValidatorKind},
Parsed,
};
use std::path::Path;

/// Check if a file is a source file
fn is_matching_file(file: &Path) -> bool {
file.is_file_kind(FileKind::Src)
}

#[must_use]
/// Validates that source files have SPDX license headers.
pub fn validate(parsed: &Parsed) -> Vec<InvalidItem> {
if !is_matching_file(&parsed.file) {
return Vec::new();
}

let mut invalid_items: Vec<InvalidItem> = Vec::new();

// Check if SPDX header is present
if find_spdx_header(&parsed.src).is_none() {
// Create a simple location for file-level issues
let loc = solang_parser::pt::Loc::File(0, 0, 0);
invalid_items.push(InvalidItem::new(
ValidatorKind::Src,
parsed,
loc,
"Missing SPDX-License-Identifier header".to_string(),
));
}

invalid_items
}

/// Check if a line is a comment line
fn is_comment_line(line: &str) -> bool {
line.starts_with("//") || line.starts_with("/*")
}

/// Check if a line contains a valid SPDX header
fn has_spdx_header(line: &str) -> bool {
line.starts_with("// SPDX-License-Identifier:")
}

/// Find SPDX header in header section
fn find_spdx_header(src: &str) -> Option<&str> {
for line in src.lines() {
let trimmed = line.trim();

// Skip empty lines
if trimmed.is_empty() {
continue;
}

// Check if this comment line has SPDX
if is_comment_line(trimmed) && has_spdx_header(trimmed) {
return Some(trimmed);
}

// If we hit any non-comment content, stop looking
if !is_comment_line(trimmed) {
break;
}
}

None
}

#[cfg(test)]
mod tests {
use super::*;
use crate::check::utils::ExpectedFindings;

#[test]
fn test_validate() {
let content = r"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Test {
uint256 public number;
}
";

ExpectedFindings::new(0).assert_eq(content, &validate);
}

#[test]
fn test_validate_missing_spdx() {
let content = r"
pragma solidity ^0.8.17;

contract Test {
uint256 public number;
}
";

let expected_findings = ExpectedFindings { src: 1, ..ExpectedFindings::default() };
expected_findings.assert_eq(content, &validate);
}

#[test]
fn test_validate_comment_then_spdx() {
let content = r"
// This is a comment
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Test {
uint256 public number;
}
";

ExpectedFindings::new(0).assert_eq(content, &validate);
}

#[test]
fn test_validate_pragma_then_spdx() {
let content = r"
pragma solidity ^0.8.17;
// SPDX-License-Identifier: MIT

contract Test {
uint256 public number;
}
";

let expected_findings = ExpectedFindings { src: 1, ..ExpectedFindings::default() };
expected_findings.assert_eq(content, &validate);
}

#[test]
fn test_validate_comment_then_pragma() {
let content = r"
// This is a comment
pragma solidity ^0.8.17;

contract Test {
uint256 public number;
}
";

let expected_findings = ExpectedFindings { src: 1, ..ExpectedFindings::default() };
expected_findings.assert_eq(content, &validate);
}
}
1 change: 1 addition & 0 deletions tests/check-proj2-NoFindings/src/Counter.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Counter {
Expand Down
1 change: 1 addition & 0 deletions tests/check-proj2-NoFindings/src/Library.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

library MyLibrary {
Expand Down
5 changes: 5 additions & 0 deletions tests/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ fn test_check_proj1_all_findings() {
"Invalid constant or immutable name in ./src/Counter.sol on line 5: badImmutable",
"Invalid constant or immutable name in ./src/Counter.sol on line 6: bad_constant",
"Invalid constant or immutable name in ./test/Counter.t.sol on line 7: testVal",
"Invalid src method name in ./src/Counter.sol on line 1: Missing SPDX-License-Identifier header",
"Invalid src method name in ./src/Counter.sol on line 23: internalShouldHaveLeadingUnderscore",
"Invalid src method name in ./src/Counter.sol on line 25: privateShouldHaveLeadingUnderscore",
"Invalid src method name in ./src/CounterIgnored1.sol on line 1: Missing SPDX-License-Identifier header",
"Invalid src method name in ./src/CounterIgnored2.sol on line 1: Missing SPDX-License-Identifier header",
"Invalid src method name in ./src/CounterIgnored3.sol on line 1: Missing SPDX-License-Identifier header",
"Invalid src method name in ./src/CounterIgnored4.sol on line 1: Missing SPDX-License-Identifier header",
"Invalid src method name in ./src/CounterIgnored4.sol on line 29: missingLeadingUnderscoreAndNotIgnored",
"Invalid test name in ./test/Counter.t.sol on line 16: testIncrementBadName",
"Invalid directive in ./src/Counter.sol: Invalid inline config item: this directive is invalid",
Expand Down
Loading