Skip to content

Commit

Permalink
pylint: W1508 invalid-envvar-default (#3449)
Browse files Browse the repository at this point in the history
  • Loading branch information
latonis committed Mar 11, 2023
1 parent 12a6fc7 commit 0f78f27
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import os

tempVar = os.getenv("TEST", 12) # [invalid-envvar-default]
goodVar = os.getenv("TESTING", None)
dictVarBad = os.getenv("AAA", {"a", 7}) # [invalid-envvar-default]
print(os.getenv("TEST", False)) # [invalid-envvar-default]
os.getenv("AA", "GOOD")
os.getenv("B", Z)
3 changes: 3 additions & 0 deletions crates/ruff/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2866,6 +2866,9 @@ where
if self.settings.rules.enabled(&Rule::BadStrStripCall) {
pylint::rules::bad_str_strip_call(self, func, args);
}
if self.settings.rules.enabled(&Rule::InvalidEnvvarDefault) {
pylint::rules::invalid_envvar_default(self, func, args, keywords);
}

// flake8-pytest-style
if self.settings.rules.enabled(&Rule::PatchWithLambda) {
Expand Down
1 change: 1 addition & 0 deletions crates/ruff/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pylint, "E0118") => Rule::UsedPriorGlobalDeclaration,
(Pylint, "E0604") => Rule::InvalidAllObject,
(Pylint, "E0605") => Rule::InvalidAllFormat,
(Pylint, "W1508") => Rule::InvalidEnvvarDefault,
(Pylint, "E1142") => Rule::AwaitOutsideAsync,
(Pylint, "E1205") => Rule::LoggingTooManyArgs,
(Pylint, "E1206") => Rule::LoggingTooFewArgs,
Expand Down
1 change: 1 addition & 0 deletions crates/ruff/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ ruff_macros::register_rules!(
rules::pylint::rules::YieldInInit,
rules::pylint::rules::InvalidAllObject,
rules::pylint::rules::InvalidAllFormat,
rules::pylint::rules::InvalidEnvvarDefault,
rules::pylint::rules::BadStringFormatType,
rules::pylint::rules::BidirectionalUnicode,
rules::pylint::rules::BadStrStripCall,
Expand Down
1 change: 1 addition & 0 deletions crates/ruff/src/rules/pylint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ mod tests {
#[test_case(Rule::GlobalStatement, Path::new("global_statement.py"); "PLW0603")]
#[test_case(Rule::InvalidAllFormat, Path::new("invalid_all_format.py"); "PLE0605")]
#[test_case(Rule::InvalidAllObject, Path::new("invalid_all_object.py"); "PLE0604")]
#[test_case(Rule::InvalidEnvvarDefault, Path::new("invalid_envvar_default.py"); "PLW1508")]
#[test_case(Rule::TooManyReturnStatements, Path::new("too_many_return_statements.py"); "PLR0911")]
#[test_case(Rule::TooManyArguments, Path::new("too_many_arguments.py"); "PLR0913")]
#[test_case(Rule::TooManyBranches, Path::new("too_many_branches.py"); "PLR0912")]
Expand Down
90 changes: 90 additions & 0 deletions crates/ruff/src/rules/pylint/rules/invalid_envvar_default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword};

use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::Range;

use crate::checkers::ast::Checker;

/// ## What it does
/// Checks for `env.getenv` calls with invalid default values.
///
/// ## Why is this bad?
/// If an environment variable is set, `env.getenv` will return its value as
/// a string. If the environment variable is _not_ set, `env.getenv` will
/// return `None`, or the default value if one is provided.
///
/// If the default value is not a string or `None`, then it will be
/// inconsistent with the return type of `env.getenv`, which can lead to
/// confusing behavior.
///
/// ## Example
/// ```python
/// int(env.getenv("FOO", 1))
/// ```
///
/// Use instead:
/// ```python
/// int(env.getenv("FOO", "1"))
/// ```
#[violation]
pub struct InvalidEnvvarDefault;

impl Violation for InvalidEnvvarDefault {
#[derive_message_formats]
fn message(&self) -> String {
format!("Invalid type for environment variable default; expected `str` or `None`")
}
}

fn is_valid_default(expr: &Expr) -> bool {
// We can't infer the types of these defaults, so assume they're valid.
if matches!(
expr.node,
ExprKind::Name { .. }
| ExprKind::Attribute { .. }
| ExprKind::Subscript { .. }
| ExprKind::Call { .. }
) {
return true;
}

// Otherwise, the default must be a string or `None`.
matches!(
expr.node,
ExprKind::Constant {
value: Constant::Str { .. } | Constant::None { .. },
..
}
)
}

/// PLW1508
pub fn invalid_envvar_default(
checker: &mut Checker,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
) {
if checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| call_path.as_slice() == ["os", "getenv"])
{
// Find the `default` argument, if it exists.
let Some(expr) = args.get(1).or_else(|| {
keywords
.iter()
.find(|keyword| keyword.node.arg.as_ref().map_or(false, |arg| arg == "default"))
.map(|keyword| &keyword.node.value)
}) else {
return;
};

if !is_valid_default(expr) {
checker
.diagnostics
.push(Diagnostic::new(InvalidEnvvarDefault, Range::from(expr)));
}
}
}
2 changes: 2 additions & 0 deletions crates/ruff/src/rules/pylint/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub use global_statement::{global_statement, GlobalStatement};
pub use global_variable_not_assigned::GlobalVariableNotAssigned;
pub use invalid_all_format::{invalid_all_format, InvalidAllFormat};
pub use invalid_all_object::{invalid_all_object, InvalidAllObject};
pub use invalid_envvar_default::{invalid_envvar_default, InvalidEnvvarDefault};
pub use logging::{logging_call, LoggingTooFewArgs, LoggingTooManyArgs};
pub use magic_value_comparison::{magic_value_comparison, MagicValueComparison};
pub use merge_isinstance::{merge_isinstance, ConsiderMergingIsinstance};
Expand Down Expand Up @@ -44,6 +45,7 @@ mod global_statement;
mod global_variable_not_assigned;
mod invalid_all_format;
mod invalid_all_object;
mod invalid_envvar_default;
mod logging;
mod magic_value_comparison;
mod merge_isinstance;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
source: crates/ruff/src/rules/pylint/mod.rs
expression: diagnostics
---
- kind:
name: InvalidEnvvarDefault
body: "Invalid type for environment variable default; expected `str` or `None`"
suggestion: ~
fixable: false
location:
row: 3
column: 28
end_location:
row: 3
column: 30
fix: ~
parent: ~
- kind:
name: InvalidEnvvarDefault
body: "Invalid type for environment variable default; expected `str` or `None`"
suggestion: ~
fixable: false
location:
row: 5
column: 30
end_location:
row: 5
column: 38
fix: ~
parent: ~
- kind:
name: InvalidEnvvarDefault
body: "Invalid type for environment variable default; expected `str` or `None`"
suggestion: ~
fixable: false
location:
row: 6
column: 24
end_location:
row: 6
column: 29
fix: ~
parent: ~

4 changes: 4 additions & 0 deletions ruff.schema.json

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

0 comments on commit 0f78f27

Please sign in to comment.