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
5 changes: 5 additions & 0 deletions cargo_pup_lint_config/src/function_lint/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ impl FunctionMatcher {
pub fn returns_self_mut_ref(&self) -> FunctionMatchNode {
FunctionMatchNode::Leaf(FunctionMatch::ReturnsType(ReturnTypePattern::SelfMutRef))
}

/// Matches async functions
pub fn is_async(&self) -> FunctionMatchNode {
FunctionMatchNode::Leaf(FunctionMatch::IsAsync)
}
}

/// Node in the matcher expression tree
Expand Down
2 changes: 2 additions & 0 deletions cargo_pup_lint_config/src/function_lint/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ pub enum FunctionMatch {
InModule(String),
/// Match functions that return a specific type pattern
ReturnsType(ReturnTypePattern),
/// Match async functions
IsAsync,
/// Logical AND - both patterns must match
AndMatches(Box<FunctionMatch>, Box<FunctionMatch>),
/// Logical OR - either pattern must match
Expand Down
25 changes: 25 additions & 0 deletions cargo_pup_lint_impl/src/lints/function_lint/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,31 @@ fn evaluate_function_match(
}
}
}
FunctionMatch::IsAsync => {
// Check if the function is async by examining the HIR
if let Some(local_def_id) = fn_def_id.as_local() {
let node = ctx.tcx.hir_node_by_def_id(local_def_id);
match node {
rustc_hir::Node::Item(item) => {
if let rustc_hir::ItemKind::Fn { sig, .. } = &item.kind {
return matches!(sig.header.asyncness, rustc_hir::IsAsync::Async(_));
}
}
rustc_hir::Node::TraitItem(trait_item) => {
if let rustc_hir::TraitItemKind::Fn(sig, _) = &trait_item.kind {
return matches!(sig.header.asyncness, rustc_hir::IsAsync::Async(_));
}
}
rustc_hir::Node::ImplItem(impl_item) => {
if let rustc_hir::ImplItemKind::Fn(sig, _) = &impl_item.kind {
return matches!(sig.header.asyncness, rustc_hir::IsAsync::Async(_));
}
}
_ => {}
}
}
false
}
FunctionMatch::AndMatches(left, right) => {
evaluate_function_match(left, ctx, module_path, function_name, fn_def_id)
&& evaluate_function_match(right, ctx, module_path, function_name, fn_def_id)
Expand Down
4 changes: 2 additions & 2 deletions test_app/Cargo.lock

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

38 changes: 37 additions & 1 deletion test_app/expected_output
Original file line number Diff line number Diff line change
Expand Up @@ -259,5 +259,41 @@ error: Function 'set_height' is forbidden by lint rule
= help: Remove this function to satisfy the architectural rule
= note: Applied by cargo-pup rule 'builder_style_set_consuming_forbidden'.

error: Function 'forbidden_async_function' is forbidden by lint rule
--> src/async_functions/mod.rs:8:1
|
8 | pub async fn forbidden_async_function() -> String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: Remove this function to satisfy the architectural rule
= note: Applied by cargo-pup rule 'async_functions_forbidden'.

error: Function 'async_result_function' is forbidden by lint rule
--> src/async_functions/mod.rs:13:1
|
13 | pub async fn async_result_function() -> Result<i32, String> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: Remove this function to satisfy the architectural rule
= note: Applied by cargo-pup rule 'async_functions_forbidden'.

error: Function 'process_async' is forbidden by lint rule
--> src/async_functions/mod.rs:33:5
|
33 | pub async fn process_async(&self) -> Result<String, String> {
| ^^^^^^^^^^^^^^^^^^
|
= help: Remove this function to satisfy the architectural rule
= note: Applied by cargo-pup rule 'async_functions_forbidden'.

error: Function 'process_item' is forbidden by lint rule
--> src/async_functions/mod.rs:55:5
|
55 | async fn process_item(&self, item: String) -> Result<String, String> {
| ^^^^^^^^^^^^^^^^^
|
= help: Remove this function to satisfy the architectural rule
= note: Applied by cargo-pup rule 'async_functions_forbidden'.

warning: `test_app` (bin "test_app") generated 20 warnings
error: could not compile `test_app` (bin "test_app") due to 2 previous errors; 20 warnings emitted
error: could not compile `test_app` (bin "test_app") due to 6 previous errors; 20 warnings emitted
10 changes: 10 additions & 0 deletions test_app/pup.ron
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,15 @@
MustNotExist(Error),
],
)),
Function((
name: "async_functions_forbidden",
matches: AndMatches(
InModule("^test_app::async_functions$"),
IsAsync
),
rules: [
MustNotExist(Error),
],
)),
],
)
62 changes: 62 additions & 0 deletions test_app/src/async_functions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/) Copyright 2024 Datadog, Inc.

//! This module demonstrates async function detection with cargo-pup

use std::future::Future;

/// This async function should trigger the IsAsync lint rule
pub async fn forbidden_async_function() -> String {
"This is an async function".to_string()
}

/// Another async function that returns a Result
pub async fn async_result_function() -> Result<i32, String> {
Ok(42)
}

/// Regular sync function that should NOT trigger the IsAsync rule
pub fn allowed_sync_function() -> String {
"This is a sync function".to_string()
}

/// A struct with async methods
pub struct AsyncService {
name: String,
}

impl AsyncService {
pub fn new(name: String) -> Self {
Self { name }
}

/// Async method that should trigger the IsAsync rule
pub async fn process_async(&self) -> Result<String, String> {
Ok(format!("Processing {}", self.name))
}

/// Sync method that should NOT trigger the IsAsync rule
pub fn process_sync(&self) -> String {
format!("Sync processing {}", self.name)
}
}

/// Trait with async methods
pub trait AsyncProcessor {
/// Async trait method
async fn process_item(&self, item: String) -> Result<String, String>;

/// Sync trait method
fn validate_item(&self, item: &str) -> bool;
}

/// Implementation with async methods
impl AsyncProcessor for AsyncService {
/// This async implementation should trigger the IsAsync rule
async fn process_item(&self, item: String) -> Result<String, String> {
Ok(format!("{}: {}", self.name, item))
}

fn validate_item(&self, item: &str) -> bool {
!item.is_empty()
}
}
1 change: 1 addition & 0 deletions test_app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod must_be_empty;
mod result_error;
mod trait_impl;
mod builder_style;
mod async_functions;

fn main() {
println!("Hello, world!");
Expand Down
50 changes: 50 additions & 0 deletions tests/ui/function_lint/is_async.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/) Copyright 2024 Datadog, Inc.

//@compile-flags: --crate-name test_is_async
//@compile-flags: --crate-type lib

// This test verifies that the FunctionLint's IsAsync matcher works correctly

// Async function that should trigger the lint
async fn async_function() { //~ ERROR: Function 'async_function' is forbidden by lint rule
}

// Async function with return type
async fn async_with_return() -> String { //~ ERROR: Function 'async_with_return' is forbidden by lint rule
"hello".to_string()
}

// Regular function that should NOT trigger the lint
fn sync_function() {
}

// Another regular function with return type
fn sync_with_return() -> String {
"hello".to_string()
}

// Async method in impl block
struct TestStruct;

impl TestStruct {
async fn async_method(&self) { //~ ERROR: Function 'async_method' is forbidden by lint rule
}

fn sync_method(&self) {
}
}

// Async functions in traits
trait AsyncTrait {
async fn trait_async_method(&self);

fn trait_sync_method(&self);
}

impl AsyncTrait for TestStruct {
async fn trait_async_method(&self) { //~ ERROR: Function 'trait_async_method' is forbidden by lint rule
}

fn trait_sync_method(&self) {
}
}
39 changes: 39 additions & 0 deletions tests/ui/function_lint/is_async.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
error: Function 'async_function' is forbidden by lint rule
--> tests/ui/function_lint/is_async.rs:9:1
|
LL | async fn async_function() {
| ^^^^^^^^^^^^^^^^^^^
|
= help: Remove this function to satisfy the architectural rule
= note: Applied by cargo-pup rule 'async_function_test'.
= note: `#[deny(function_lint)]` on by default

error: Function 'async_with_return' is forbidden by lint rule
--> tests/ui/function_lint/is_async.rs:13:1
|
LL | async fn async_with_return() -> String {
| ^^^^^^^^^^^^^^^^^^^^^^
|
= help: Remove this function to satisfy the architectural rule
= note: Applied by cargo-pup rule 'async_function_test'.

error: Function 'async_method' is forbidden by lint rule
--> tests/ui/function_lint/is_async.rs:30:5
|
LL | async fn async_method(&self) {
| ^^^^^^^^^^^^^^^^^
|
= help: Remove this function to satisfy the architectural rule
= note: Applied by cargo-pup rule 'async_function_test'.

error: Function 'trait_async_method' is forbidden by lint rule
--> tests/ui/function_lint/is_async.rs:45:5
|
LL | async fn trait_async_method(&self) {
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= help: Remove this function to satisfy the architectural rule
= note: Applied by cargo-pup rule 'async_function_test'.

error: aborting due to 4 previous errors

17 changes: 17 additions & 0 deletions tests/ui/function_lint/pup.ron
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,23 @@
)
]
)
),

// ======================================================================
// SECTION: IsAsync Tests (for is_async.rs)
// ======================================================================

// Test that async functions are detected and can be linted
Function(
(
name: "async_function_test",
matches: IsAsync,
rules: [
MustNotExist(
Error,
)
]
)
)
]
)