Skip to content

Commit c2e6cb2

Browse files
committed
use a separate entrypoint which directly checks the function definition
1 parent d96f362 commit c2e6cb2

File tree

2 files changed

+54
-41
lines changed

2 files changed

+54
-41
lines changed

crates/ruff_linter/src/checkers/ast/analyze/statement.rs

+3
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
376376
if checker.enabled(Rule::PytestParameterWithDefaultArgument) {
377377
flake8_pytest_style::rules::parameter_with_default_argument(checker, function_def);
378378
}
379+
if checker.enabled(Rule::Airflow3Removal) {
380+
airflow::rules::removed_in_3_function_def(checker, function_def);
381+
}
379382
if checker.enabled(Rule::NonPEP695GenericFunction) {
380383
pyupgrade::rules::non_pep695_generic_function(checker, function_def);
381384
}

crates/ruff_linter/src/rules/airflow/rules/removal_in_3.rs

+51-41
Original file line numberDiff line numberDiff line change
@@ -347,16 +347,6 @@ fn find_parameter<'a>(
347347
/// print(context.get("conf"))
348348
/// ```
349349
///
350-
/// **Removed context variable as a parameter:**
351-
/// ```python
352-
/// from airflow.decorators import task
353-
///
354-
/// @task
355-
/// def another_task(execution_date, **kwargs):
356-
/// # 'execution_date' is removed in Airflow 3.0
357-
/// pass
358-
/// ```
359-
///
360350
/// **Accessing multiple keys:**
361351
/// ```python
362352
/// from airflow.decorators import task
@@ -403,37 +393,6 @@ fn check_removed_context_keys_usage(checker: &mut Checker, call_expr: &ExprCall)
403393
if attr.as_str() != "get" {
404394
return;
405395
}
406-
let function_def = {
407-
let mut parents = checker.semantic().current_statements();
408-
parents.find_map(|stmt| {
409-
if let Stmt::FunctionDef(func_def) = stmt {
410-
Some(func_def.clone())
411-
} else {
412-
None
413-
}
414-
})
415-
};
416-
417-
if let Some(func_def) = function_def {
418-
for param in func_def
419-
.parameters
420-
.posonlyargs
421-
.iter()
422-
.chain(func_def.parameters.args.iter())
423-
.chain(func_def.parameters.kwonlyargs.iter())
424-
{
425-
let param_name = param.parameter.name.as_str();
426-
if REMOVED_CONTEXT_KEYS.contains(&param_name) {
427-
checker.diagnostics.push(Diagnostic::new(
428-
Airflow3Removal {
429-
deprecated: param_name.to_string(),
430-
replacement: Replacement::None,
431-
},
432-
param.parameter.name.range(),
433-
));
434-
}
435-
}
436-
}
437396

438397
for removed_key in REMOVED_CONTEXT_KEYS {
439398
if let Some(argument) = call_expr.arguments.find_argument_value(removed_key, 0) {
@@ -1092,3 +1051,54 @@ fn is_airflow_builtin_or_provider(segments: &[&str], module: &str, symbol_suffix
10921051
_ => false,
10931052
}
10941053
}
1054+
1055+
/// AIR302 Check the function argument for removed context variable.
1056+
/// For example:
1057+
/// **Removed context variable as a parameter:**
1058+
/// ```python
1059+
/// from airflow.decorators import task
1060+
///
1061+
/// @task
1062+
/// def another_task(execution_date, **kwargs):
1063+
/// # 'execution_date' is removed in Airflow 3.0
1064+
/// pass
1065+
/// ```
1066+
pub(crate) fn removed_in_3_function_def(checker: &mut Checker, function_def: &StmtFunctionDef) {
1067+
if !checker.semantic().seen_module(Modules::AIRFLOW) {
1068+
return;
1069+
}
1070+
1071+
if !is_airflow_task(function_def, checker.semantic()) {
1072+
return;
1073+
}
1074+
1075+
for param in function_def
1076+
.parameters
1077+
.posonlyargs
1078+
.iter()
1079+
.chain(function_def.parameters.args.iter())
1080+
.chain(function_def.parameters.kwonlyargs.iter())
1081+
{
1082+
let param_name = param.parameter.name.as_str();
1083+
if REMOVED_CONTEXT_KEYS.contains(&param_name) {
1084+
checker.diagnostics.push(Diagnostic::new(
1085+
Airflow3Removal {
1086+
deprecated: param_name.to_string(),
1087+
replacement: Replacement::None,
1088+
},
1089+
param.parameter.name.range(),
1090+
));
1091+
}
1092+
}
1093+
}
1094+
1095+
/// Returns `true` if the given function is decorated with `@airflow.decorators.task`.
1096+
fn is_airflow_task(function_def: &StmtFunctionDef, semantic: &SemanticModel) -> bool {
1097+
function_def.decorator_list.iter().any(|decorator| {
1098+
semantic
1099+
.resolve_qualified_name(map_callable(&decorator.expression))
1100+
.is_some_and(|qualified_name| {
1101+
matches!(qualified_name.segments(), ["airflow", "decorators", "task"])
1102+
})
1103+
})
1104+
}

0 commit comments

Comments
 (0)