pub(crate) mod cformat;
pub(crate) mod fixes;
pub(crate) mod format;
pub(crate) mod rules;
pub mod settings;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use regex::Regex;
use ruff_db::diagnostic::Diagnostic;
use ruff_python_parser::ParseOptions;
use rustc_hash::FxHashMap;
use test_case::test_case;
use ruff_python_ast::PySourceType;
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_trivia::textwrap::dedent;
use crate::linter::check_path;
use crate::registry::{Linter, Rule};
use crate::rules::isort;
use crate::rules::pyflakes;
use crate::settings::types::PreviewMode;
use crate::settings::{LinterSettings, flags};
use crate::source_kind::SourceKind;
use crate::suppression::Suppressions;
use crate::test::{test_contents, test_path, test_snippet};
use crate::{Locator, assert_diagnostics, assert_diagnostics_diff, directives};
#[test_case(Rule::UnusedImport, Path::new("F401_0.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_1.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_2.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_3.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_4.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_5.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_6.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_7.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_8.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_9.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_10.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_11.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_12.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_13.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_14.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_15.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_16.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_17.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_18.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_19.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_20.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_21.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_22.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_23.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_32.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_34.py"))]
#[test_case(Rule::ImportShadowedByLoopVar, Path::new("F402.py"))]
#[test_case(Rule::ImportShadowedByLoopVar, Path::new("F402.ipynb"))]
#[test_case(Rule::UndefinedLocalWithImportStar, Path::new("F403.py"))]
#[test_case(Rule::LateFutureImport, Path::new("F404_0.py"))]
#[test_case(Rule::LateFutureImport, Path::new("F404_1.py"))]
#[test_case(Rule::UndefinedLocalWithImportStarUsage, Path::new("F405.py"))]
#[test_case(Rule::UndefinedLocalWithNestedImportStarUsage, Path::new("F406.py"))]
#[test_case(Rule::FutureFeatureNotDefined, Path::new("F407.py"))]
#[test_case(Rule::PercentFormatInvalidFormat, Path::new("F50x.py"))]
#[test_case(Rule::PercentFormatExpectedMapping, Path::new("F502.py"))]
#[test_case(Rule::PercentFormatExpectedMapping, Path::new("F50x.py"))]
#[test_case(Rule::PercentFormatExpectedSequence, Path::new("F503.py"))]
#[test_case(Rule::PercentFormatExpectedSequence, Path::new("F50x.py"))]
#[test_case(Rule::PercentFormatExtraNamedArguments, Path::new("F504.py"))]
#[test_case(Rule::PercentFormatExtraNamedArguments, Path::new("F50x.py"))]
#[test_case(Rule::PercentFormatMissingArgument, Path::new("F504.py"))]
#[test_case(Rule::PercentFormatMissingArgument, Path::new("F50x.py"))]
#[test_case(Rule::PercentFormatMixedPositionalAndNamed, Path::new("F50x.py"))]
#[test_case(Rule::PercentFormatPositionalCountMismatch, Path::new("F50x.py"))]
#[test_case(Rule::PercentFormatStarRequiresSequence, Path::new("F50x.py"))]
#[test_case(Rule::PercentFormatUnsupportedFormatCharacter, Path::new("F50x.py"))]
#[test_case(Rule::StringDotFormatInvalidFormat, Path::new("F521.py"))]
#[test_case(Rule::StringDotFormatExtraNamedArguments, Path::new("F522.py"))]
#[test_case(Rule::StringDotFormatExtraPositionalArguments, Path::new("F523.py"))]
#[test_case(Rule::StringDotFormatMissingArguments, Path::new("F524.py"))]
#[test_case(Rule::StringDotFormatMixingAutomatic, Path::new("F525.py"))]
#[test_case(Rule::FStringMissingPlaceholders, Path::new("F541.py"))]
#[test_case(Rule::MultiValueRepeatedKeyLiteral, Path::new("F601.py"))]
#[test_case(Rule::MultiValueRepeatedKeyVariable, Path::new("F602.py"))]
#[test_case(Rule::MultipleStarredExpressions, Path::new("F622.py"))]
#[test_case(Rule::AssertTuple, Path::new("F631.py"))]
#[test_case(Rule::IsLiteral, Path::new("F632.py"))]
#[test_case(Rule::InvalidPrintSyntax, Path::new("F633.py"))]
#[test_case(Rule::IfTuple, Path::new("F634.py"))]
#[test_case(Rule::BreakOutsideLoop, Path::new("F701.py"))]
#[test_case(Rule::ContinueOutsideLoop, Path::new("F702.py"))]
#[test_case(Rule::YieldOutsideFunction, Path::new("F704.py"))]
#[test_case(Rule::ReturnOutsideFunction, Path::new("F706.py"))]
#[test_case(Rule::DefaultExceptNotLast, Path::new("F707.py"))]
#[test_case(Rule::ForwardAnnotationSyntaxError, Path::new("F722.py"))]
#[test_case(Rule::ForwardAnnotationSyntaxError, Path::new("F722_1.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_0.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_1.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_2.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_3.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_4.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_5.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_6.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_7.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_8.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_9.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_10.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_11.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_12.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_13.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_14.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_15.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_16.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_17.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_18.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_19.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_20.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_21.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_22.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_23.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_24.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_25.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_26.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_27.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_28.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_29.pyi"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_30.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_31.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_32.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_33.pyi"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_35.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_36.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_0.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_1.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_2.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_3.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_4.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_5.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_5.pyi"))]
#[test_case(Rule::UndefinedName, Path::new("F821_6.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_7.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_8.pyi"))]
#[test_case(Rule::UndefinedName, Path::new("F821_9.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_10.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_11.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_11.pyi"))]
#[test_case(Rule::UndefinedName, Path::new("F821_12.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_13.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_14.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_15.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_16.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_17.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_18.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_19.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_20.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_21.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_22.ipynb"))]
#[test_case(Rule::UndefinedName, Path::new("F821_23.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_24.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_25.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_26.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_26.pyi"))]
#[test_case(Rule::UndefinedName, Path::new("F821_27.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_28.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_30.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_31.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_32.pyi"))]
#[test_case(Rule::UndefinedName, Path::new("F821_33.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_34.pyi"))]
#[test_case(Rule::UndefinedName, Path::new("F821_34.py"))]
#[test_case(Rule::UndefinedExport, Path::new("F822_0.py"))]
#[test_case(Rule::UndefinedExport, Path::new("F822_0.pyi"))]
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))]
#[test_case(Rule::UndefinedExport, Path::new("F822_1b.py"))]
#[test_case(Rule::UndefinedExport, Path::new("F822_2.py"))]
#[test_case(Rule::UndefinedExport, Path::new("F822_3.py"))]
#[test_case(Rule::UndefinedLocal, Path::new("F823.py"))]
#[test_case(Rule::UnusedVariable, Path::new("F841_0.py"))]
#[test_case(Rule::UnusedVariable, Path::new("F841_1.py"))]
#[test_case(Rule::UnusedVariable, Path::new("F841_2.py"))]
#[test_case(Rule::UnusedVariable, Path::new("F841_3.py"))]
#[test_case(Rule::UnusedAnnotation, Path::new("F842.py"))]
#[test_case(Rule::RaiseNotImplemented, Path::new("F901.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("pyflakes").join(path).as_path(),
&LinterSettings::for_rule(rule_code),
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::UndefinedName, Path::new("F821_29.py"))]
fn rules_with_flake8_type_checking_settings_enabled(
rule_code: Rule,
path: &Path,
) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("pyflakes").join(path).as_path(),
&LinterSettings {
flake8_type_checking: crate::rules::flake8_type_checking::settings::Settings {
runtime_required_base_classes: vec![
"pydantic.BaseModel".to_string(),
"sqlalchemy.orm.DeclarativeBase".to_string(),
],
..Default::default()
},
..LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test]
fn f821_with_builtin_added_on_new_py_version_but_old_target_version_specified() {
let diagnostics = test_snippet(
"PythonFinalizationError",
&LinterSettings {
unresolved_target_version: ruff_python_ast::PythonVersion::PY312.into(),
..LinterSettings::for_rule(Rule::UndefinedName)
},
);
assert_diagnostics!(diagnostics);
}
#[test]
fn f821_frozendict_py315_available() {
let diagnostics = test_snippet(
"frozendict",
&LinterSettings {
unresolved_target_version: ruff_python_ast::PythonVersion::PY315.into(),
..LinterSettings::for_rule(Rule::UndefinedName)
},
);
assert!(diagnostics.is_empty());
}
#[test]
fn f821_frozendict_pre_py315_undefined() {
let diagnostics = test_snippet(
"frozendict",
&LinterSettings {
unresolved_target_version: ruff_python_ast::PythonVersion::PY314.into(),
..LinterSettings::for_rule(Rule::UndefinedName)
},
);
assert_diagnostics!(diagnostics);
}
#[test_case(Rule::UnusedImport, Path::new("__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_24/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_25__all_nonempty/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_26__all_empty/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_27__all_mistyped/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_28__all_multiple/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_29__all_conditional/__init__.py"))]
#[test_case(Rule::UndefinedExport, Path::new("__init__.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_36.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("pyflakes").join(path).as_path(),
&LinterSettings {
preview: PreviewMode::Enabled,
..LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(
r"import submodule.a",
"f401_preview_first_party_submodule_no_dunder_all"
)]
#[test_case(
r"
import submodule.a
__all__ = ['FOO']
FOO = 42",
"f401_preview_first_party_submodule_dunder_all"
)]
#[test_case(
r"
import submodule.bar
import submodule.baz
__all__ = ['submodule']
FOO = 42",
"f401_preview_dunder_all_multiple_bindings"
)]
fn f401_preview_first_party_submodule(contents: &str, snapshot: &str) {
let diagnostics = test_contents(
&SourceKind::Python {
code: dedent(contents).to_string(),
is_stub: false,
},
Path::new("f401_preview_first_party_submodule/__init__.py"),
&LinterSettings {
preview: PreviewMode::Enabled,
isort: isort::settings::Settings {
known_modules: isort::categorize::KnownModules::new(
vec!["submodule".parse().unwrap()],
vec![],
vec![],
vec![],
FxHashMap::default(),
),
..isort::settings::Settings::default()
},
..LinterSettings::for_rule(Rule::UnusedImport)
},
)
.0;
assert_diagnostics!(snapshot, diagnostics);
}
#[test_case(Rule::UnusedImport, Path::new("F401_33/__init__.py"))]
fn f401_preview_local_init_import(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let settings = LinterSettings {
preview: PreviewMode::Enabled,
isort: isort::settings::Settings {
known_modules: isort::categorize::KnownModules::new(
vec!["F401_*".parse()?],
vec![],
vec![],
vec![],
FxHashMap::default(),
),
..isort::settings::Settings::default()
},
..LinterSettings::for_rule(rule_code)
};
let diagnostics = test_path(Path::new("pyflakes").join(path).as_path(), &settings)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::UnusedImport, Path::new("F401_24/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_25__all_nonempty/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_26__all_empty/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_27__all_mistyped/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_28__all_multiple/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_29__all_conditional/__init__.py"))]
fn f401_stable(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"{}_stable_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("pyflakes").join(path).as_path(),
&LinterSettings::for_rule(rule_code),
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::UnusedImport, Path::new("F401_24/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_25__all_nonempty/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_26__all_empty/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_27__all_mistyped/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_28__all_multiple/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_29__all_conditional/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_30.py"))]
fn f401_deprecated_option(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"{}_deprecated_option_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("pyflakes").join(path).as_path(),
&LinterSettings {
ignore_init_module_imports: false,
..LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::UnusedImport, Path::new("F401_31.py"))]
fn f401_allowed_unused_imports_option(rule_code: Rule, path: &Path) -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes").join(path).as_path(),
&LinterSettings {
pyflakes: pyflakes::settings::Settings {
allowed_unused_imports: vec!["hvplot.pandas".to_string()],
..pyflakes::settings::Settings::default()
},
..LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test_case(Rule::UnusedImport, Path::new("F401_35.py"))]
fn f401_allowed_unused_imports_top_level_module(rule_code: Rule, path: &Path) -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes").join(path).as_path(),
&LinterSettings {
pyflakes: pyflakes::settings::Settings {
allowed_unused_imports: vec!["hvplot".to_string()],
..pyflakes::settings::Settings::default()
},
..LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test_case(Rule::UnusedImport, Path::new("F401_0.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_1.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_2.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_3.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_4.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_5.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_6.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_7.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_8.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_9.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_10.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_11.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_12.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_13.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_14.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_15.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_16.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_17.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_18.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_19.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_20.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_21.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_22.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_23.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_32.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_34.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_35.py"))]
fn f401_preview_refined_submodule_handling_diffs(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("preview_diff__{}", path.to_string_lossy());
assert_diagnostics_diff!(
snapshot,
Path::new("pyflakes").join(path).as_path(),
&LinterSettings::for_rule(rule_code),
&LinterSettings {
preview: PreviewMode::Enabled,
..LinterSettings::for_rule(rule_code)
}
);
Ok(())
}
#[test_case(
r"
import a
import a.b
import a.c",
"f401_multiple_unused_submodules"
)]
#[test_case(
r"
import a
import a.b
a.foo()",
"f401_use_top_member"
)]
#[test_case(
r"
import a
import a.b
a.foo()
a.bar()",
"f401_use_top_member_twice"
)]
#[test_case(
r"
# reverts to stable behavior - used between imports
import a
a.foo()
import a.b",
"f401_use_top_member_before_second_import"
)]
#[test_case(
r"
# reverts to stable behavior - used between imports
import a
a.foo()
a = 1
import a.b",
"f401_use_top_member_and_redefine_before_second_import"
)]
#[test_case(
r"
# reverts to stable behavior - used between imports
import a
a.foo()
import a.b
a = 1",
"f401_use_top_member_then_import_then_redefine"
)]
#[test_case(
r#"
import a
import a.b
__all__ = ["a"]"#,
"f401_use_in_dunder_all"
)]
#[test_case(
r"
import a.c
import a.b
a.foo()",
"f401_import_submodules_but_use_top_level"
)]
#[test_case(
r"
import a.c
import a.b.d
a.foo()",
"f401_import_submodules_different_lengths_but_use_top_level"
)]
#[test_case(
r"
# refined logic only applied _within_ scope
import a
def foo():
import a.b
a.foo()",
"f401_import_submodules_in_function_scope"
)]
#[test_case(
r"
# reverts to stable behavior - used between bindings
import a
a.b
import a.b",
"f401_use_in_between_imports"
)]
#[test_case(
r"
# reverts to stable behavior - used between bindings
import a.b
a
import a",
"f401_use_in_between_imports"
)]
#[test_case(
r"
if cond:
import a
import a.b
a.foo()
",
"f401_same_branch"
)]
#[test_case(
r"
try:
import a.b.c
except ImportError:
import argparse
import a
a.b = argparse.Namespace()
",
"f401_different_branch"
)]
#[test_case(
r"
import mlflow.pyfunc.loaders.chat_agent
import mlflow.pyfunc.loaders.chat_model
import mlflow.pyfunc.loaders.code_model
from mlflow.utils.pydantic_utils import IS_PYDANTIC_V2_OR_NEWER
if IS_PYDANTIC_V2_OR_NEWER:
import mlflow.pyfunc.loaders.responses_agent
",
"f401_type_checking"
)]
fn f401_preview_refined_submodule_handling(contents: &str, snapshot: &str) {
let diagnostics = test_contents(
&SourceKind::Python {
code: dedent(contents).to_string(),
is_stub: false,
},
Path::new("f401_preview_submodule.py"),
&LinterSettings {
preview: PreviewMode::Enabled,
..LinterSettings::for_rule(Rule::UnusedImport)
},
)
.0;
assert_diagnostics!(snapshot, diagnostics);
}
#[test]
fn f841_dummy_variable_rgx() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/F841_0.py"),
&LinterSettings {
dummy_variable_rgx: Regex::new(r"^z$").unwrap(),
..LinterSettings::for_rule(Rule::UnusedVariable)
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn init() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/__init__.py"),
&LinterSettings::for_rules(vec![
Rule::UndefinedName,
Rule::UndefinedExport,
Rule::UnusedImport,
]),
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn default_builtins() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/builtins.py"),
&LinterSettings::for_rules(vec![Rule::UndefinedName]),
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn extra_builtins() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/builtins.py"),
&LinterSettings {
builtins: vec!["_".to_string()],
..LinterSettings::for_rules(vec![Rule::UndefinedName])
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn default_typing_modules() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/typing_modules.py"),
&LinterSettings::for_rules(vec![Rule::UndefinedName]),
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn extra_typing_modules() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/typing_modules.py"),
&LinterSettings {
typing_modules: vec!["airflow.typing_compat".to_string()],
..LinterSettings::for_rules(vec![Rule::UndefinedName])
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn future_annotations() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/future_annotations.py"),
&LinterSettings::for_rules(vec![Rule::UnusedImport, Rule::UndefinedName]),
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn multi_statement_lines() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/multi_statement_lines.py"),
&LinterSettings::for_rule(Rule::UnusedImport),
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn relative_typing_module() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/project/foo/bar.py"),
&LinterSettings {
typing_modules: vec!["foo.typical".to_string()],
..LinterSettings::for_rule(Rule::UndefinedName)
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn nested_relative_typing_module() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/project/foo/bop/baz.py"),
&LinterSettings {
typing_modules: vec!["foo.typical".to_string()],
..LinterSettings::for_rule(Rule::UndefinedName)
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn f811_typing_modules_overload() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/F811_33.py"),
&LinterSettings {
typing_modules: vec!["std".to_string()],
..LinterSettings::for_rule(Rule::RedefinedWhileUnused)
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn f811_annotated_assignment_redefinition() -> Result<()> {
let diagnostics = test_path(
Path::new("pyflakes/F811_34.py"),
&LinterSettings {
preview: PreviewMode::Enabled,
..LinterSettings::for_rule(Rule::RedefinedWhileUnused)
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn extend_generics() -> Result<()> {
let snapshot = "extend_immutable_calls".to_string();
let diagnostics = test_path(
Path::new("pyflakes/F401_15.py"),
&LinterSettings {
pyflakes: pyflakes::settings::Settings {
extend_generics: vec!["django.db.models.ForeignKey".to_string()],
..pyflakes::settings::Settings::default()
},
..LinterSettings::for_rule(Rule::UnusedImport)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(
r"
import os
def f():
import os
# Despite this `del`, `import os` in `f` should still be flagged as shadowing an unused
# import.
del os
",
"del_shadowed_global_import_in_local_scope"
)]
#[test_case(
r"
import os
def f():
import os
# Despite this `del`, `import os` in `f` should still be flagged as shadowing an unused
# import. (This is a false negative, but is consistent with Pyflakes.)
del os
",
"del_shadowed_global_import_in_global_scope"
)]
#[test_case(
r"
def f():
import os
import os
# Despite this `del`, `import os` should still be flagged as shadowing an unused
# import.
del os
",
"del_shadowed_local_import_in_local_scope"
)]
#[test_case(
r"
import os
def f():
os = 1
print(os)
del os
def g():
# `import os` doesn't need to be flagged as shadowing an import.
os = 1
print(os)
",
"del_shadowed_import_shadow_in_local_scope"
)]
#[test_case(
r"
x = 1
def foo():
x = 2
del x
# Flake8 treats this as an F823 error, because it removes the binding
# entirely after the `del` statement. However, it should be an F821
# error, because the name is defined in the scope, but unbound.
x += 1
",
"augmented_assignment_after_del"
)]
#[test_case(
r"
def f():
x = 1
try:
1 / 0
except Exception as x:
pass
# No error here, though it should arguably be an F821 error. `x` will
# be unbound after the `except` block (assuming an exception is raised
# and caught).
print(x)
",
"print_in_body_after_shadowing_except"
)]
#[test_case(
r"
def f():
x = 1
try:
1 / 0
except ValueError as x:
pass
except ImportError as x:
pass
# No error here, though it should arguably be an F821 error. `x` will
# be unbound after the `except` block (assuming an exception is raised
# and caught).
print(x)
",
"print_in_body_after_double_shadowing_except"
)]
#[test_case(
r"
def f():
try:
x = 3
except ImportError as x:
print(x)
else:
print(x)
",
"print_in_try_else_after_shadowing_except"
)]
#[test_case(
r"
def f():
list = [1, 2, 3]
for e in list:
if e % 2 == 0:
try:
pass
except Exception as e:
print(e)
else:
print(e)
",
"print_in_if_else_after_shadowing_except"
)]
#[test_case(
r"
def f():
x = 1
del x
del x
",
"double_del"
)]
#[test_case(
r"
x = 1
def f():
try:
pass
except ValueError as x:
pass
# This should resolve to the `x` in `x = 1`.
print(x)
",
"load_after_unbind_from_module_scope"
)]
#[test_case(
r"
x = 1
def f():
try:
pass
except ValueError as x:
pass
try:
pass
except ValueError as x:
pass
# This should resolve to the `x` in `x = 1`.
print(x)
",
"load_after_multiple_unbinds_from_module_scope"
)]
#[test_case(
r"
x = 1
def f():
try:
pass
except ValueError as x:
pass
def g():
try:
pass
except ValueError as x:
pass
# This should resolve to the `x` in `x = 1`.
print(x)
",
"load_after_unbind_from_nested_module_scope"
)]
#[test_case(
r"
class C:
x = 1
def f():
try:
pass
except ValueError as x:
pass
# This should raise an F821 error, rather than resolving to the
# `x` in `x = 1`.
print(x)
",
"load_after_unbind_from_class_scope"
)]
fn contents(contents: &str, snapshot: &str) {
let diagnostics = test_snippet(
contents,
&LinterSettings::for_rules(Linter::Pyflakes.rules()),
);
assert_diagnostics!(snapshot, diagnostics);
}
#[track_caller]
fn flakes(contents: &str, expected: &[Rule]) {
let contents = dedent(contents);
let source_type = PySourceType::default();
let source_kind = SourceKind::Python {
code: contents.to_string(),
is_stub: source_type.is_stub(),
};
let settings = LinterSettings::for_rules(Linter::Pyflakes.rules());
let target_version = settings.unresolved_target_version;
let options =
ParseOptions::from(source_type).with_target_version(target_version.parser_version());
let parsed = ruff_python_parser::parse_unchecked(source_kind.source_code(), options)
.try_into_module()
.expect("PySourceType always parses into a module");
let locator = Locator::new(&contents);
let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents());
let indexer = Indexer::from_tokens(parsed.tokens(), locator.contents());
let directives = directives::extract_directives(
parsed.tokens(),
directives::Flags::from_settings(&settings),
&locator,
&indexer,
);
let suppressions =
Suppressions::from_tokens(locator.contents(), parsed.tokens(), &indexer, &settings);
let mut messages = check_path(
Path::new("<filename>"),
None,
&locator,
&stylist,
&indexer,
&directives,
&settings,
flags::Noqa::Enabled,
&source_kind,
source_type,
&parsed,
target_version,
&suppressions,
);
messages.sort_by(Diagnostic::ruff_start_ordering);
let actual = messages
.iter()
.filter(|msg| !msg.is_invalid_syntax())
.map(Diagnostic::name)
.collect::<Vec<_>>();
let expected: Vec<_> = expected.iter().map(|rule| rule.name().as_str()).collect();
assert_eq!(actual, expected);
}
#[test]
fn undefined() {
flakes("bar", &[Rule::UndefinedName]);
}
#[test]
fn defined_in_list_comp() {
flakes("[a for a in range(10) if a]", &[]);
}
#[test]
fn undefined_in_list_comp() {
flakes(
r"
[a for a in range(10)]
a
",
&[Rule::UndefinedName],
);
}
#[test]
fn undefined_exception_name() {
flakes(
r"
try:
raise ValueError('ve')
except ValueError as exc:
pass
exc
",
&[Rule::UnusedVariable, Rule::UndefinedName],
);
}
#[test]
fn names_declared_in_except_blocks() {
flakes(
r"
try:
raise ValueError('ve')
except ValueError as exc:
e = exc
e
",
&[],
);
}
#[test]
fn undefined_exception_name_obscuring_local_variable2() {
flakes(
r"
try:
raise ValueError('ve')
except ValueError as exc:
pass
print(exc)
exc = 'Original value'
",
&[Rule::UnusedVariable, Rule::UndefinedName],
);
}
#[test]
fn del_exception_in_except() {
flakes(
r"
try:
pass
except Exception as exc:
del exc
",
&[],
);
}
#[test]
fn functions_need_global_scope() {
flakes(
r"
class a:
def b():
fu
fu = 1
",
&[],
);
}
#[test]
fn builtins() {
flakes("range(10)", &[]);
}
#[test]
fn builtin_windows_error() {
flakes("WindowsError", &[]);
}
#[test]
fn module_annotations() {
flakes("__annotations__", &[]);
}
#[test]
fn module_warningregistry() {
flakes("__warningregistry__", &[]);
}
#[test]
fn module_annotate_py314_available() {
let diagnostics = crate::test::test_snippet(
"__annotate__",
&crate::settings::LinterSettings {
unresolved_target_version: ruff_python_ast::PythonVersion::PY314.into(),
..crate::settings::LinterSettings::for_rules(vec![
crate::codes::Rule::UndefinedName,
])
},
);
assert!(diagnostics.is_empty());
}
#[test]
fn module_annotate_pre_py314_undefined() {
let diagnostics = crate::test::test_snippet(
"__annotate__",
&crate::settings::LinterSettings {
unresolved_target_version: ruff_python_ast::PythonVersion::PY313.into(),
..crate::settings::LinterSettings::for_rules(vec![
crate::codes::Rule::UndefinedName,
])
},
);
assert_eq!(diagnostics.len(), 1);
}
#[test]
fn magic_globals_file() {
flakes("__file__", &[]);
}
#[test]
fn magic_globals_builtins() {
flakes("__builtins__", &[]);
}
#[test]
fn magic_globals_name() {
flakes("__name__", &[]);
}
#[test]
fn magic_module_in_class_scope() {
flakes("__module__", &[Rule::UndefinedName]);
flakes(
r"
class Foo:
__module__
",
&[],
);
flakes(
r"
class Foo:
def bar(self):
__module__
",
&[Rule::UndefinedName],
);
}
#[test]
fn magic_qualname_in_class_scope() {
flakes("__qualname__", &[Rule::UndefinedName]);
flakes(
r"
class Foo:
__qualname__
",
&[],
);
flakes(
r"
class Foo:
def bar(self):
__qualname__
",
&[Rule::UndefinedName],
);
flakes(
r"
def f():
__qualname__ = 1
class Foo:
__qualname__
",
&[Rule::UnusedVariable],
);
}
#[test]
fn global_import_star() {
flakes(
"from fu import *; bar",
&[
Rule::UndefinedLocalWithImportStar,
Rule::UndefinedLocalWithImportStarUsage,
],
);
}
#[test]
fn defined_by_global() {
flakes(
r"
def a(): global fu; fu = 1
def b(): fu
",
&[],
);
flakes(
r"
def c(): bar
def b(): global bar; bar = 1
",
&[],
);
flakes(
r"
def c(): bar
def d():
def b():
global bar; bar = 1
",
&[Rule::UndefinedName],
);
}
#[test]
fn defined_by_global_multiple_names() {
flakes(
r"
def a(): global fu, bar; fu = 1; bar = 2
def b(): fu; bar
",
&[],
);
}
#[test]
fn global_in_global_scope() {
flakes(
r"
global x
def foo():
print(x)
",
&[Rule::UndefinedName],
);
}
#[test]
fn global_reset_name_only() {
flakes(
r"
def f1():
s
def f2():
global m
",
&[Rule::UndefinedName],
);
}
#[test]
fn del() {
flakes("a = 1; del a; a", &[Rule::UndefinedName]);
}
#[test]
fn del_global() {
flakes(
r"
a = 1
def f():
global a
del a
a
",
&[],
);
}
#[test]
fn del_undefined() {
flakes("del a", &[Rule::UndefinedName]);
}
#[test]
fn del_conditional() {
flakes(
r"
context = None
test = True
if False:
del(test)
assert(test)
",
&[],
);
}
#[test]
fn del_conditional_except() {
flakes(
r"
context = None
test = True
try:
...
except Exception:
del(test)
else:
assert(test)
",
&[],
);
}
#[test]
fn del_conditional_orelse() {
flakes(
r"
context = None
test = True
try:
...
except Exception:
print(test)
else:
del test
assert(test)
",
&[],
);
}
#[test]
fn del_conditional_nested() {
flakes(
r"
context = None
test = True
if False:
with context():
del(test)
assert(test)
",
&[],
);
}
#[test]
fn del_while() {
flakes(
r"
def test():
foo = 'bar'
while False:
del foo
assert(foo)
",
&[],
);
}
#[test]
fn del_while_test_usage() {
flakes(
r"
def _worker():
o = True
while o is not True:
del o
o = False
",
&[],
);
}
#[test]
fn del_while_nested() {
flakes(
r"
context = None
def _worker():
o = True
while o is not True:
while True:
with context():
del o
o = False
",
&[],
);
}
#[test]
fn global_from_nested_scope() {
flakes(
r"
a = 1
def b():
def c():
a
",
&[],
);
}
#[test]
fn later_redefined_global_from_nested_scope() {
flakes(
r"
a = 1
def fun():
a
a = 2
return a
",
&[Rule::UndefinedLocal],
);
}
#[test]
fn later_redefined_global_from_nested_scope2() {
flakes(
r"
a = 1
def fun():
global a
def fun2():
a
a = 2
return a
",
&[Rule::UndefinedLocal],
);
}
#[test]
fn intermediate_class_scope_ignored() {
flakes(
r"
def f():
x = 1
class g:
def h(self):
a = x
x = None
print(x, a)
print(x)
",
&[Rule::UndefinedLocal],
);
}
#[test]
fn later_redefined_global_from_nested_scope3() {
flakes(
r"
def fun():
a = 1
def fun2():
a
a = 1
return a
return a
",
&[Rule::UndefinedLocal],
);
}
#[test]
fn undefined_augmented_assignment() {
flakes(
r"
def f(seq):
a = 0
seq[a] += 1
seq[b] /= 2
c[0] *= 2
a -= 3
d += 4
e[any] = 5
",
&[
Rule::UndefinedName,
Rule::UndefinedName,
Rule::UndefinedName,
Rule::UnusedVariable,
Rule::UndefinedName,
],
);
}
#[test]
fn nested_class() {
flakes(
r"
def f(foo):
class C:
bar = foo
def f(self):
return foo
return C()
f(123).f()
",
&[],
);
}
#[test]
fn bad_nested_class() {
flakes(
r"
def f():
class C:
bar = foo
foo = 456
return foo
f()
",
&[Rule::UndefinedName],
);
}
#[test]
fn defined_as_star_args() {
flakes(
r"
def f(a, *b, **c):
print(a, b, c)
",
&[],
);
}
#[test]
fn defined_as_star_unpack() {
flakes(
r"
a, *b = range(10)
print(a, b)
",
&[],
);
flakes(
r"
*a, b = range(10)
print(a, b)
",
&[],
);
flakes(
r"
a, *b, c = range(10)
print(a, b, c)
",
&[],
);
}
#[test]
fn used_as_star_unpack() {
flakes(
r"
def f():
a, *b = range(10)
",
&[],
);
flakes(
r"
def f():
(*a, b) = range(10)
",
&[],
);
flakes(
r"
def f():
[a, *b, c] = range(10)
",
&[],
);
}
#[test]
fn keyword_only_args() {
flakes(
r"
def f(*, a, b=None):
print(a, b)
",
&[],
);
flakes(
r"
import default_b
def f(*, a, b=default_b):
print(a, b)
",
&[],
);
}
#[test]
fn keyword_only_args_undefined() {
flakes(
r"
def f(*, a, b=default_c):
print(a, b)
",
&[Rule::UndefinedName],
);
}
#[test]
fn annotation_undefined() {
flakes(
r"
from abc import note1, note2, note3, note4, note5
def func(a: note1, *args: note2,
b: note3=12, **kw: note4) -> note5: pass
",
&[],
);
flakes(
r"
def func():
d = e = 42
def func(a: {1, d}) -> (lambda c: e): pass
",
&[],
);
}
#[test]
fn meta_class_undefined() {
flakes(
r"
from abc import ABCMeta
class A(metaclass=ABCMeta): pass
",
&[],
);
}
#[test]
fn defined_in_gen_exp() {
flakes("(a for a in [1, 2, 3] if a)", &[]);
flakes("(b for b in (a for a in [1, 2, 3] if a) if b)", &[]);
}
#[test]
fn undefined_in_gen_exp_nested() {
flakes(
"(b for b in (a for a in [1, 2, 3] if b) if b)",
&[Rule::UndefinedName],
);
flakes(
"(b for b in (a for a in [1, 2, 3] if a) if a)",
&[Rule::UndefinedName],
);
}
#[test]
fn undefined_with_error_handler() {
flakes(
r"
try:
socket_map
except NameError:
socket_map = {}
",
&[],
);
flakes(
r#"
try:
_memoryview.contiguous
except (NameError, AttributeError):
raise RuntimeError("Python >= 3.3 is required")
"#,
&[],
);
flakes(
r"
try:
socket_map
except:
socket_map = {}
",
&[Rule::UndefinedName],
);
flakes(
r"
try:
socket_map
except Exception:
socket_map = {}
",
&[Rule::UndefinedName],
);
}
#[test]
fn defined_in_class() {
flakes(
r"
class A:
T = range(10)
Z = (x for x in T)
L = [x for x in T]
B = dict((i, str(i)) for i in T)
",
&[],
);
flakes(
r"
class A:
T = range(10)
X = {x for x in T}
Y = {x:x for x in T}
",
&[],
);
flakes(
r"
class A:
T = 1
# In each case, `T` is undefined. Only the first `iter` uses the class scope.
X = (T for x in range(10))
Y = [x for x in range(10) if T]
Z = [x for x in range(10) for y in T]
",
&[
Rule::UndefinedName,
Rule::UndefinedName,
Rule::UndefinedName,
],
);
}
#[test]
fn defined_in_class_nested() {
flakes(
r"
class A:
T = range(10)
Z = (x for x in (a for a in T))
",
&[],
);
}
#[test]
fn undefined_in_loop() {
flakes(
r"
for i in range(i):
print(i)
",
&[Rule::UndefinedName],
);
flakes(
r"
[42 for i in range(i)]
",
&[Rule::UndefinedName],
);
flakes(
r"
(42 for i in range(i))
",
&[Rule::UndefinedName],
);
}
#[test]
fn defined_from_lambda_in_dictionary_comprehension() {
flakes(
r"
{lambda: id(x) for x in range(10)}
",
&[],
);
}
#[test]
fn defined_from_lambda_in_generator() {
flakes(
r"
any(lambda: id(x) for x in range(10))
",
&[],
);
}
#[test]
fn undefined_from_lambda_in_dictionary_comprehension() {
flakes(
r"
{lambda: id(y) for x in range(10)}
",
&[Rule::UndefinedName],
);
}
#[test]
fn undefined_from_lambda_in_comprehension() {
flakes(
r"
any(lambda: id(y) for x in range(10))
",
&[Rule::UndefinedName],
);
}
#[test]
fn dunder_class() {
flakes(
r"
class Test(object):
def __init__(self):
print(__class__.__name__)
self.x = 1
t = Test()
",
&[],
);
flakes(
r"
class Test(object):
print(__class__.__name__)
def __init__(self):
self.x = 1
t = Test()
",
&[Rule::UndefinedName],
);
flakes(
r"
class Test(object):
X = [__class__ for _ in range(10)]
def __init__(self):
self.x = 1
t = Test()
",
&[Rule::UndefinedName],
);
flakes(
r"
def f(self):
print(__class__.__name__)
self.x = 1
f()
",
&[Rule::UndefinedName],
);
}
#[test]
fn unused_import() {
flakes("import fu, bar", &[Rule::UnusedImport, Rule::UnusedImport]);
flakes(
"from baz import fu, bar",
&[Rule::UnusedImport, Rule::UnusedImport],
);
}
#[test]
fn unused_import_relative() {
flakes("from . import fu", &[Rule::UnusedImport]);
flakes("from . import fu as baz", &[Rule::UnusedImport]);
flakes("from .. import fu", &[Rule::UnusedImport]);
flakes("from ... import fu", &[Rule::UnusedImport]);
flakes("from .. import fu as baz", &[Rule::UnusedImport]);
flakes("from .bar import fu", &[Rule::UnusedImport]);
flakes("from ..bar import fu", &[Rule::UnusedImport]);
flakes("from ...bar import fu", &[Rule::UnusedImport]);
flakes("from ...bar import fu as baz", &[Rule::UnusedImport]);
}
#[test]
fn aliased_import() {
flakes(
"import fu as FU, bar as FU",
&[Rule::RedefinedWhileUnused, Rule::UnusedImport],
);
flakes(
"from moo import fu as FU, bar as FU",
&[Rule::RedefinedWhileUnused, Rule::UnusedImport],
);
}
#[test]
fn aliased_import_shadow_module() {
flakes("from moo import fu as moo; moo", &[]);
flakes("import fu as fu; fu", &[]);
flakes("import fu.bar as fu; fu", &[]);
}
#[test]
fn used_import() {
flakes("import fu; print(fu)", &[]);
flakes("from baz import fu; print(fu)", &[]);
flakes("import fu; del fu", &[]);
}
#[test]
fn used_import_relative() {
flakes("from . import fu; assert fu", &[]);
flakes("from .bar import fu; assert fu", &[]);
flakes("from .. import fu; assert fu", &[]);
flakes("from ..bar import fu as baz; assert baz", &[]);
}
#[test]
fn redefined_while_unused() {
flakes("import fu; fu = 3", &[Rule::RedefinedWhileUnused]);
flakes("import fu; fu, bar = 3", &[Rule::RedefinedWhileUnused]);
flakes("import fu; [fu, bar] = 3", &[Rule::RedefinedWhileUnused]);
}
#[test]
fn redefined_if() {
flakes(
r"
i = 2
if i==1:
import os
import os
os.path
",
&[Rule::RedefinedWhileUnused],
);
}
#[test]
fn redefined_if_else() {
flakes(
r"
i = 2
if i==1:
import os
else:
import os
os.path
",
&[],
);
}
#[test]
fn redefined_try() {
flakes(
r"
try:
import os
import os
except:
pass
os.path
",
&[Rule::RedefinedWhileUnused],
);
}
#[test]
fn redefined_try_except() {
flakes(
r"
try:
import os
except:
import os
os.path
",
&[],
);
}
#[test]
fn redefined_try_nested() {
flakes(
r"
try:
if True:
if True:
import os
except:
import os
os.path
",
&[],
);
}
#[test]
fn redefined_try_except_multi() {
flakes(
r"
try:
from aa import mixer
except AttributeError:
from bb import mixer
except RuntimeError:
from cc import mixer
except:
from dd import mixer
mixer(123)
",
&[],
);
}
#[test]
fn redefined_try_else() {
flakes(
r"
try:
from aa import mixer
except ImportError:
pass
else:
from bb import mixer
mixer(123)
",
&[Rule::RedefinedWhileUnused],
);
}
#[test]
fn redefined_try_except_else() {
flakes(
r"
try:
import funca
except ImportError:
from bb import funca
from bb import funcb
else:
from bbb import funcb
print(funca, funcb)
",
&[],
);
}
#[test]
fn redefined_try_except_finally() {
flakes(
r"
try:
from aa import a
except ImportError:
from bb import a
finally:
a = 42
print(a)
",
&[],
);
}
#[test]
fn redefined_try_except_else_finally() {
flakes(
r"
try:
import b
except ImportError:
b = Ellipsis
from bb import a
else:
from aa import a
finally:
a = 42
print(a, b)
",
&[],
);
}
#[test]
fn redefined_by_function() {
flakes(
r"
import fu
def fu():
pass
",
&[Rule::RedefinedWhileUnused],
);
}
#[test]
fn redefined_in_nested_function() {
flakes(
r"
import fu
def bar():
def baz():
def fu():
pass
",
&[Rule::UnusedImport, Rule::RedefinedWhileUnused],
);
}
#[test]
fn redefined_in_nested_function_twice() {
flakes(
r"
import fu
def bar():
import fu
def baz():
def fu():
pass
",
&[
Rule::UnusedImport,
Rule::RedefinedWhileUnused,
Rule::UnusedImport,
Rule::RedefinedWhileUnused,
],
);
}
#[test]
fn redefined_but_used_later() {
flakes(
r"
import unittest, transport
class GetTransportTestCase(unittest.TestCase):
def test_get_transport(self):
transport = 'transport'
self.assertIsNotNone(transport)
class TestTransportMethodArgs(unittest.TestCase):
def test_send_defaults(self):
transport.Transport()",
&[],
);
}
#[test]
fn redefined_by_class() {
flakes(
r"
import fu
class fu:
pass
",
&[Rule::RedefinedWhileUnused],
);
}
#[test]
fn redefined_by_subclass() {
flakes(
r"
from fu import bar
class bar(bar):
pass
",
&[],
);
}
#[test]
fn redefined_in_class() {
flakes(
r"
import fu
class bar:
fu = 1
print(fu)
",
&[],
);
}
#[test]
fn import_in_class() {
flakes(
r"
class bar:
import fu
",
&[],
);
flakes(
r"
class bar:
import fu
fu
",
&[Rule::UndefinedName],
);
}
#[test]
fn used_in_function() {
flakes(
r"
import fu
def fun():
print(fu)
",
&[],
);
}
#[test]
fn shadowed_by_parameter() {
flakes(
r"
import fu
def fun(fu):
print(fu)
",
&[Rule::UnusedImport, Rule::RedefinedWhileUnused],
);
flakes(
r"
import fu
def fun(fu):
print(fu)
print(fu)
",
&[],
);
}
#[test]
fn new_assignment() {
flakes("fu = None", &[]);
}
#[test]
fn used_in_getattr() {
flakes("import fu; fu.bar.baz", &[]);
flakes("import fu; \"bar\".fu.baz", &[Rule::UnusedImport]);
}
#[test]
fn used_in_slice() {
flakes("import fu; print(fu.bar[1:])", &[]);
}
#[test]
fn used_in_if_body() {
flakes(
r"
import fu
if True: print(fu)
",
&[],
);
}
#[test]
fn used_in_if_conditional() {
flakes(
r"
import fu
if fu: pass
",
&[],
);
}
#[test]
fn used_in_elif_conditional() {
flakes(
r"
import fu
if False: pass
elif fu: pass
",
&[],
);
}
#[test]
fn used_in_else() {
flakes(
r"
import fu
if False: pass
else: print(fu)
",
&[],
);
}
#[test]
fn used_in_call() {
flakes("import fu; fu.bar()", &[]);
}
#[test]
fn used_in_class() {
flakes(
r"
import fu
class bar:
bar = fu
",
&[],
);
}
#[test]
fn used_in_class_base() {
flakes(
r"
import fu
class bar(object, fu.baz):
pass
",
&[],
);
}
#[test]
fn not_used_in_nested_scope() {
flakes(
r"
import fu
def bleh():
pass
print(fu)
",
&[],
);
}
#[test]
fn used_in_for() {
flakes(
r"
import fu
for bar in range(9):
print(fu)
",
&[],
);
}
#[test]
fn used_in_for_else() {
flakes(
r"
import fu
for bar in range(10):
pass
else:
print(fu)
",
&[],
);
}
#[test]
fn redefined_by_for() {
flakes(
r"
import fu
for fu in range(2):
pass
",
&[Rule::ImportShadowedByLoopVar],
);
}
#[test]
fn shadowed_by_for() {
flakes(
r"
import fu
fu.bar()
for fu in ():
pass
",
&[Rule::ImportShadowedByLoopVar],
);
}
#[test]
fn shadowed_by_for_deep() {
flakes(
r"
import fu
fu.bar()
for (x, y, z, (a, b, c, (fu,))) in ():
pass
",
&[Rule::ImportShadowedByLoopVar],
);
flakes(
r"
import fu
fu.bar()
for [x, y, z, (a, b, c, (fu,))] in ():
pass
",
&[Rule::ImportShadowedByLoopVar],
);
}
#[test]
fn used_in_return() {
flakes(
r"
import fu
def fun():
return fu
",
&[],
);
}
#[test]
fn used_in_operators() {
flakes("import fu; 3 + fu.bar", &[]);
flakes("import fu; 3 % fu.bar", &[]);
flakes("import fu; 3 - fu.bar", &[]);
flakes("import fu; 3 * fu.bar", &[]);
flakes("import fu; 3 ** fu.bar", &[]);
flakes("import fu; 3 / fu.bar", &[]);
flakes("import fu; 3 // fu.bar", &[]);
flakes("import fu; -fu.bar", &[]);
flakes("import fu; ~fu.bar", &[]);
flakes("import fu; 1 == fu.bar", &[]);
flakes("import fu; 1 | fu.bar", &[]);
flakes("import fu; 1 & fu.bar", &[]);
flakes("import fu; 1 ^ fu.bar", &[]);
flakes("import fu; 1 >> fu.bar", &[]);
flakes("import fu; 1 << fu.bar", &[]);
}
#[test]
fn used_in_assert() {
flakes("import fu; assert fu.bar", &[]);
}
#[test]
fn used_in_subscript() {
flakes("import fu; fu.bar[1]", &[]);
}
#[test]
fn used_in_logic() {
flakes("import fu; fu and False", &[]);
flakes("import fu; fu or False", &[]);
flakes("import fu; not fu.bar", &[]);
}
#[test]
fn used_in_list() {
flakes("import fu; [fu]", &[]);
}
#[test]
fn used_in_tuple() {
flakes("import fu; (fu,)", &[]);
}
#[test]
fn used_in_try() {
flakes(
r"
import fu
try: fu
except: pass
",
&[],
);
}
#[test]
fn used_in_except() {
flakes(
r"
import fu
try: fu
except: pass
",
&[],
);
}
#[test]
fn redefined_by_except() {
flakes(
r"
import fu
try: pass
except Exception as fu: pass
",
&[Rule::UnusedVariable, Rule::RedefinedWhileUnused],
);
}
#[test]
fn used_in_raise() {
flakes(
r"
import fu
raise fu.bar
",
&[],
);
}
#[test]
fn used_in_yield() {
flakes(
r"
import fu
def gen():
yield fu
",
&[],
);
}
#[test]
fn used_in_dict() {
flakes("import fu; {fu:None}", &[]);
flakes("import fu; {1:fu}", &[]);
}
#[test]
fn used_in_parameter_default() {
flakes(
r"
import fu
def f(bar=fu):
pass
",
&[],
);
}
#[test]
fn used_in_attribute_assign() {
flakes("import fu; fu.bar = 1", &[]);
}
#[test]
fn used_in_keyword_arg() {
flakes("import fu; fu.bar(stuff=fu)", &[]);
}
#[test]
fn used_in_assignment() {
flakes("import fu; bar=fu", &[]);
flakes("import fu; n=0; n+=fu", &[]);
}
#[test]
fn used_in_list_comp() {
flakes("import fu; [fu for _ in range(1)]", &[]);
flakes("import fu; [1 for _ in range(1) if fu]", &[]);
}
#[test]
fn used_in_try_finally() {
flakes(
r"
import fu
try: pass
finally: fu
",
&[],
);
flakes(
r"
import fu
try: fu
finally: pass
",
&[],
);
}
#[test]
fn used_in_while() {
flakes(
r"
import fu
while 0:
fu
",
&[],
);
flakes(
r"
import fu
while fu: pass
",
&[],
);
}
#[test]
fn used_in_global() {
flakes(
r"
import fu
def f(): global fu
",
&[Rule::UnusedImport],
);
}
#[test]
fn used_and_global() {
flakes(
r"
import foo
def f(): global foo
def g(): foo.is_used()
",
&[],
);
}
#[test]
fn assigned_to_global() {
flakes(
r"
def f(): global foo; import foo
def g(): foo.is_used()
",
&[],
);
}
#[test]
fn used_in_exec() {
flakes("import fu; exec('print(1)', fu.bar)", &[]);
}
#[test]
fn used_in_lambda() {
flakes(
r"import fu;
lambda: fu
",
&[],
);
}
#[test]
fn shadowed_by_lambda() {
flakes(
"import fu; lambda fu: fu",
&[Rule::UnusedImport, Rule::RedefinedWhileUnused],
);
flakes("import fu; lambda fu: fu\nfu()", &[]);
}
#[test]
fn used_in_slice_obj() {
flakes(
r#"import fu;
"meow"[::fu]
"#,
&[],
);
}
#[test]
fn unused_in_nested_scope() {
flakes(
r"
def bar():
import fu
fu
",
&[Rule::UnusedImport, Rule::UndefinedName],
);
}
#[test]
fn methods_dont_use_class_scope() {
flakes(
r"
class bar:
import fu
def fun(self):
fu
",
&[Rule::UndefinedName],
);
}
#[test]
fn nested_functions_nest_scope() {
flakes(
r"
def a():
def b():
fu
import fu
",
&[],
);
}
#[test]
fn nested_class_and_function_scope() {
flakes(
r"
def a():
import fu
class b:
def c(self):
print(fu)
",
&[],
);
}
#[test]
fn package_import() {
flakes(
r"
import fu.bar
fu.bar
",
&[],
);
}
#[test]
fn unused_package_import() {
flakes("import fu.bar", &[Rule::UnusedImport]);
}
#[test]
fn duplicate_submodule_import() {
flakes(
r"
import fu.bar, fu.bar
fu.bar
",
&[Rule::RedefinedWhileUnused],
);
flakes(
r"
import fu.bar
import fu.bar
fu.bar
",
&[Rule::RedefinedWhileUnused],
);
}
#[test]
fn different_submodule_import() {
flakes(
r"
import fu.bar, fu.baz
fu.bar, fu.baz
",
&[],
);
flakes(
r"
import fu.bar
import fu.baz
fu.bar, fu.baz
",
&[],
);
}
#[test]
fn aliased_submodule_import() {
flakes(
r"
import fu.bar as baz
import fu.bar as baz
baz
",
&[Rule::RedefinedWhileUnused],
);
flakes(
r"
import fu.bar as baz
import baz
baz
",
&[Rule::RedefinedWhileUnused],
);
flakes(
r"
import fu.bar as baz
import fu.bar as bop
baz, bop
",
&[],
);
flakes(
r"
import foo.baz
import foo.baz as foo
foo
",
&[Rule::RedefinedWhileUnused],
);
}
#[test]
fn used_package_with_submodule_import() {
flakes(
r"
import fu
import fu.bar
fu.x
",
&[],
);
flakes(
r"
import fu.bar
import fu
fu.x
",
&[],
);
}
#[test]
fn used_package_with_submodule_import_of_alias() {
flakes(
r"
import foo as f
import foo.bar
f.bar.do_something()
",
&[],
);
flakes(
r"
import foo as f
import foo.bar.blah
f.bar.blah.do_something()
",
&[],
);
}
#[test]
fn unused_package_with_submodule_import() {
flakes(
r"
import fu
import fu.bar
",
&[Rule::UnusedImport],
);
}
#[test]
fn assign_rhs_first() {
flakes("import fu; fu = fu", &[]);
flakes("import fu; fu, bar = fu", &[]);
flakes("import fu; [fu, bar] = fu", &[]);
flakes("import fu; fu += fu", &[]);
}
#[test]
fn trying_multiple_imports() {
flakes(
r"
try:
import fu
except ImportError:
import bar as fu
fu
",
&[],
);
}
#[test]
fn non_global_does_not_redefine() {
flakes(
r"
import fu
def a():
fu = 3
return fu
fu
",
&[],
);
}
#[test]
fn functions_run_later() {
flakes(
r"
def a():
fu
import fu
",
&[],
);
}
#[test]
fn function_names_are_bound_now() {
flakes(
r"
import fu
def fu():
fu
fu
",
&[Rule::RedefinedWhileUnused],
);
}
#[test]
fn ignore_non_import_redefinitions() {
flakes("a = 1; a = 2", &[]);
}
#[test]
fn imported_in_class() {
flakes(
r"
class C:
import i
def __init__(self):
self.i
",
&[],
);
}
#[test]
fn import_used_in_method_definition() {
flakes(
r"
import foo
class Thing(object):
def foo(self, parser=foo.parse_foo):
pass
",
&[],
);
}
#[test]
fn future_import() {
flakes("from __future__ import division", &[]);
flakes(
r#"
"docstring is allowed before future import"
from __future__ import division
"#,
&[],
);
}
#[test]
fn future_import_first() {
flakes(
r"
x = 5
from __future__ import division
",
&[Rule::LateFutureImport],
);
flakes(
r"
from foo import bar
from __future__ import division
bar
",
&[Rule::LateFutureImport],
);
}
#[test]
fn future_import_used() {
flakes(
r"
from __future__ import division
from __future__ import print_function
assert print_function is not division
",
&[],
);
}
#[test]
fn future_import_undefined() {
flakes(
r"
from __future__ import print_statement
",
&[Rule::FutureFeatureNotDefined],
);
}
#[test]
fn future_import_star() {
flakes(
r"
from __future__ import *
",
&[Rule::FutureFeatureNotDefined],
);
}
#[test]
fn ignored_in_function() {
flakes(
r#"
def foo():
import bar
__all__ = ["bar"]
"#,
&[Rule::UnusedImport, Rule::UnusedVariable],
);
}
#[test]
fn ignored_in_class() {
flakes(
r#"
import bar
class foo:
__all__ = ["bar"]
"#,
&[Rule::UnusedImport],
);
}
#[test]
fn ignored_when_not_directly_assigned() {
flakes(
r#"
import bar
(__all__,) = ("foo",)
"#,
&[Rule::UnusedImport],
);
}
#[test]
fn warning_suppressed() {
flakes(
r#"
import foo
__all__ = ["foo"]
"#,
&[],
);
flakes(
r#"
import foo
__all__ = ("foo",)
"#,
&[],
);
}
#[test]
fn augmented_assignment() {
flakes(
r"
import a
import c
__all__ = ['a']
__all__ += ['b']
if 1 < 3:
__all__ += ['c', 'd']
",
&[Rule::UndefinedExport, Rule::UndefinedExport],
);
}
#[test]
fn list_concatenation_assignment() {
flakes(
r"
import sys
__all__ = ['a'] + ['b'] + ['c']
",
&[
Rule::UnusedImport,
Rule::UndefinedExport,
Rule::UndefinedExport,
Rule::UndefinedExport,
],
);
}
#[test]
fn tuple_concatenation_assignment() {
flakes(
r"
import sys
__all__ = ('a',) + ('b',) + ('c',)
",
&[
Rule::UnusedImport,
Rule::UndefinedExport,
Rule::UndefinedExport,
Rule::UndefinedExport,
],
);
}
#[test]
fn all_with_attributes() {
flakes(
r"
from foo import bar
__all__ = [bar.__name__]
",
&[],
);
}
#[test]
fn all_with_names() {
flakes(
r"
from foo import bar
__all__ = [bar]
",
&[],
);
}
#[test]
fn all_with_attributes_added() {
flakes(
r"
from foo import bar
from bar import baz
__all__ = [bar.__name__] + [baz.__name__]
",
&[],
);
}
#[test]
fn all_mixed_attributes_and_strings() {
flakes(
r"
from foo import bar
from foo import baz
__all__ = ['bar', baz.__name__]
",
&[],
);
}
#[test]
fn unbound_exported() {
flakes(
r#"
__all__ = ["foo"]
"#,
&[Rule::UndefinedExport],
);
}
#[test]
fn import_star_exported() {
flakes(
r"
from math import *
__all__ = ['sin', 'cos']
csc(1)
",
&[
Rule::UndefinedLocalWithImportStar,
Rule::UndefinedLocalWithImportStarUsage,
Rule::UndefinedLocalWithImportStarUsage,
Rule::UndefinedLocalWithImportStarUsage,
],
);
}
#[ignore]
#[test]
fn import_star_not_exported() {
flakes(
r"
from foolib import *
a = 1
__all__ = ['a']
",
&[Rule::UndefinedLocalWithImportStar, Rule::UnusedImport],
);
}
#[test]
fn used_in_gen_exp() {
flakes("import fu; (fu for _ in range(1))", &[]);
flakes("import fu; (1 for _ in range(1) if fu)", &[]);
}
#[test]
fn redefined_by_gen_exp() {
flakes(
"import fu; (1 for fu in range(1))",
&[Rule::UnusedImport, Rule::RedefinedWhileUnused],
);
}
#[test]
fn used_as_decorator() {
flakes(
r#"
from interior import decorate
@decorate
def f():
return "hello"
"#,
&[],
);
flakes(
r#"
@decorate
def f():
return "hello"
"#,
&[Rule::UndefinedName],
);
}
#[test]
fn used_as_class_decorator() {
flakes(
r"
from interior import decorate
@decorate
class foo:
pass
",
&[],
);
flakes(
r#"
from interior import decorate
@decorate("foo")
class bar:
pass
"#,
&[],
);
flakes(
r"
@decorate
class foo:
pass
",
&[Rule::UndefinedName],
);
}
#[test]
fn typing_overload() {
flakes(
r"
import typing
from typing import overload
@overload
def f(s: None) -> None:
pass
@overload
def f(s: int) -> int:
pass
def f(s):
return s
@typing.overload
def g(s: None) -> None:
pass
@typing.overload
def g(s: int) -> int:
pass
def g(s):
return s
",
&[],
);
}
#[test]
fn typing_extensions_overload() {
flakes(
r"
import typing_extensions
from typing_extensions import overload
@overload
def f(s: None) -> None:
pass
@overload
def f(s: int) -> int:
pass
def f(s):
return s
@typing_extensions.overload
def g(s: None) -> None:
pass
@typing_extensions.overload
def g(s: int) -> int:
pass
def g(s):
return s
",
&[],
);
}
#[test]
fn typing_overload_async() {
flakes(
r"
from typing import overload
@overload
async def f(s: None) -> None:
pass
@overload
async def f(s: int) -> int:
pass
async def f(s):
return s
",
&[],
);
}
#[test]
fn overload_with_multiple_decorators() {
flakes(
r"
from typing import overload
dec = lambda f: f
@dec
@overload
def f(x: int) -> int:
pass
@dec
@overload
def f(x: str) -> str:
pass
@dec
def f(x): return x
",
&[],
);
}
#[test]
fn overload_in_class() {
flakes(
r"
from typing import overload
class C:
@overload
def f(self, x: int) -> int:
pass
@overload
def f(self, x: str) -> str:
pass
def f(self, x): return x
",
&[],
);
}
#[test]
fn aliased_typing_import() {
flakes(
r"
import typing as t
@t.overload
def f(s: None) -> None:
pass
@t.overload
def f(s: int) -> int:
pass
def f(s):
return s
",
&[],
);
}
#[test]
fn not_a_typing_overload() {
flakes(
r"
def foo(x):
return x
@foo
def bar():
pass
def bar():
pass
",
&[Rule::RedefinedWhileUnused],
);
}
#[test]
fn variable_annotations() {
flakes(
r"
name: str
age: int
",
&[],
);
flakes(
r"
name: str = 'Bob'
age: int = 18
",
&[],
);
flakes(
r"
class C:
name: str
age: int
",
&[],
);
flakes(
r"
class C:
name: str = 'Bob'
age: int = 18
",
&[],
);
flakes(
r"
def f():
name: str
age: int
",
&[Rule::UnusedAnnotation, Rule::UnusedAnnotation],
);
flakes(
r"
def f():
name: str = 'Bob'
age: int = 18
foo: not_a_real_type = None
",
&[
Rule::UnusedVariable,
Rule::UnusedVariable,
Rule::UnusedVariable,
Rule::UndefinedName,
],
);
flakes(
r"
def f():
name: str
print(name)
",
&[Rule::UndefinedName],
);
flakes(
r"
from typing import Any
def f():
a: Any
",
&[Rule::UnusedAnnotation],
);
flakes(
r"
foo: not_a_real_type
",
&[Rule::UndefinedName],
);
flakes(
r"
foo: not_a_real_type = None
",
&[Rule::UndefinedName],
);
flakes(
r"
class C:
foo: not_a_real_type
",
&[Rule::UndefinedName],
);
flakes(
r"
class C:
foo: not_a_real_type = None
",
&[Rule::UndefinedName],
);
flakes(
r"
def f():
class C:
foo: not_a_real_type
",
&[Rule::UndefinedName],
);
flakes(
r"
def f():
class C:
foo: not_a_real_type = None
",
&[Rule::UndefinedName],
);
flakes(
r"
from foo import Bar
bar: Bar
",
&[],
);
flakes(
r"
from foo import Bar
bar: 'Bar'
",
&[],
);
flakes(
r"
import foo
bar: foo.Bar
",
&[],
);
flakes(
r"
import foo
bar: 'foo.Bar'
",
&[],
);
flakes(
r"
from foo import Bar
def f(bar: Bar): pass
",
&[],
);
flakes(
r"
from foo import Bar
def f(bar: 'Bar'): pass
",
&[],
);
flakes(
r"
from foo import Bar
def f(bar) -> Bar: return bar
",
&[],
);
flakes(
r"
from foo import Bar
def f(bar) -> 'Bar': return bar
",
&[],
);
flakes(
r"
bar: 'Bar'
",
&[Rule::UndefinedName],
);
flakes(
r"
bar: 'foo.Bar'
",
&[Rule::UndefinedName],
);
flakes(
r"
from foo import Bar
bar: str
",
&[Rule::UnusedImport],
);
flakes(
r"
from foo import Bar
def f(bar: str): pass
",
&[Rule::UnusedImport],
);
flakes(
r"
def f(a: A) -> A: pass
class A: pass
",
&[],
);
flakes(
r"
def f(a: 'A') -> 'A': return a
class A: pass
",
&[],
);
flakes(
r"
a: A
class A: pass
",
&[],
);
flakes(
r"
a: 'A'
class A: pass
",
&[],
);
flakes(
r"
T: object
def f(t: T): pass
",
&[Rule::UndefinedName],
);
flakes(
r"
T: object
def g(t: 'T'): pass
",
&[Rule::UndefinedName],
);
flakes(
r"
T = object
def f(t: T): pass
",
&[],
);
flakes(
r"
T = object
def g(t: 'T'): pass
",
&[],
);
flakes(
r"
a: 'A B'
",
&[Rule::ForwardAnnotationSyntaxError],
);
flakes(
r"
a: 'A; B'
",
&[Rule::ForwardAnnotationSyntaxError],
);
flakes(
r"
a: '1 + 2'
",
&[],
);
flakes(
r#"
a: 'a: "A"'
"#,
&[Rule::ForwardAnnotationSyntaxError],
);
}
#[test]
fn variable_annotation_references_self_name_undefined() {
flakes(
r"
x: int = x
",
&[Rule::UndefinedName],
);
}
#[test]
fn use_pep695_type_aliass() {
flakes(
r"
from typing_extensions import TypeAlias
from foo import Bar
bar: TypeAlias = Bar
",
&[],
);
flakes(
r"
from typing_extensions import TypeAlias
from foo import Bar
bar: TypeAlias = 'Bar'
",
&[],
);
flakes(
r"
from typing_extensions import TypeAlias
from foo import Bar
class A:
bar: TypeAlias = Bar
",
&[],
);
flakes(
r"
from typing_extensions import TypeAlias
from foo import Bar
class A:
bar: TypeAlias = 'Bar'
",
&[],
);
flakes(
r"
from typing_extensions import TypeAlias
bar: TypeAlias
",
&[],
);
flakes(
r"
from typing_extensions import TypeAlias
from foo import Bar
bar: TypeAlias
",
&[Rule::UnusedImport],
);
}
#[test]
fn annotating_an_import() {
flakes(
r"
from a import b, c
b: c
print(b)
",
&[],
);
}
#[test]
fn unused_annotation() {
flakes(
r"
x: int
class Cls:
y: int
",
&[],
);
flakes(
r"
def f():
x: int
",
&[Rule::UnusedAnnotation],
);
flakes(
r"
def f():
x: int
x = 3
",
&[Rule::UnusedVariable],
);
}
#[test]
fn unassigned_annotation_is_undefined() {
flakes(
r"
name: str
print(name)
",
&[Rule::UndefinedName],
);
}
#[test]
fn annotated_async_def() {
flakes(
r"
class c: pass
async def func(c: c) -> None: pass
",
&[],
);
}
#[test]
fn postponed_annotations() {
flakes(
r"
from __future__ import annotations
def f(a: A) -> A: pass
class A:
b: B
class B: pass
",
&[],
);
flakes(
r"
lazy from __future__ import annotations
",
&[Rule::UnusedImport],
);
flakes(
r"
from __future__ import annotations
def f(a: A) -> A: pass
class A:
b: Undefined
class B: pass
",
&[Rule::UndefinedName],
);
flakes(
r"
from __future__ import annotations
T: object
def f(t: T): pass
def g(t: 'T'): pass
",
&[Rule::UndefinedName, Rule::UndefinedName],
);
flakes(
r"
from __future__ import annotations
T = object
def f(t: T): pass
def g(t: 'T'): pass
",
&[],
);
}
#[test]
fn type_annotation_clobbers_all() {
flakes(
r#"
from typing import TYPE_CHECKING, List
from y import z
if not TYPE_CHECKING:
__all__ = ("z",)
else:
__all__: List[str]
"#,
&[],
);
}
#[test]
fn return_annotation_is_class_scope_variable() {
flakes(
r"
from typing import TypeVar
class Test:
Y = TypeVar('Y')
def t(self, x: Y) -> Y:
return x
",
&[],
);
}
#[test]
fn return_annotation_is_function_body_variable() {
flakes(
r"
class Test:
def t(self) -> Y:
Y = 2
return Y
",
&[Rule::UndefinedName],
);
}
#[test]
fn positional_only_argument_annotations() {
flakes(
r"
from x import C
def f(c: C, /): ...
",
&[],
);
}
#[test]
fn partially_quoted_type_annotation() {
flakes(
r"
from queue import Queue
from typing import Optional
def f() -> Optional['Queue[str]']:
return None
",
&[],
);
}
#[test]
fn partially_quoted_type_assignment() {
flakes(
r"
from queue import Queue
from typing import Optional
MaybeQueue = Optional['Queue[str]']
",
&[],
);
}
#[test]
fn nested_partially_quoted_type_assignment() {
flakes(
r"
from queue import Queue
from typing import Callable
Func = Callable[['Queue[str]'], None]
",
&[],
);
}
#[test]
fn quoted_type_cast() {
flakes(
r"
from typing import cast, Optional
maybe_int = cast('Optional[int]', 42)
",
&[],
);
}
#[test]
fn type_cast_literal_str_to_str() {
flakes(
r"
from typing import cast
a_string = cast(str, 'Optional[int]')
",
&[],
);
}
#[test]
fn quoted_type_cast_renamed_import() {
flakes(
r"
from typing import cast as tsac, Optional as Maybe
maybe_int = tsac('Maybe[int]', 42)
",
&[],
);
}
#[test]
fn quoted_type_var_constraints() {
flakes(
r"
from typing import TypeVar, Optional
T = TypeVar('T', 'str', 'Optional[int]', bytes)
",
&[],
);
}
#[test]
fn quoted_type_var_bound() {
flakes(
r"
from typing import TypeVar, Optional, List
T = TypeVar('T', bound='Optional[int]')
S = TypeVar('S', int, bound='List[int]')
",
&[],
);
}
#[test]
fn literal_type_typing() {
flakes(
r"
from typing import Literal
def f(x: Literal['some string']) -> None:
return None
",
&[],
);
}
#[test]
fn literal_type_typing_extensions() {
flakes(
r"
from typing_extensions import Literal
def f(x: Literal['some string']) -> None:
return None
",
&[],
);
}
#[test]
fn annotated_type_typing_missing_forward_type_multiple_args() {
flakes(
r"
from typing import Annotated
def f(x: Annotated['integer', 1]) -> None:
return None
",
&[Rule::UndefinedName],
);
}
#[test]
fn annotated_type_typing_with_string_args() {
flakes(
r"
from typing import Annotated
def f(x: Annotated[int, '> 0']) -> None:
return None
",
&[],
);
}
#[test]
fn annotated_type_typing_with_string_args_in_union() {
flakes(
r"
from typing import Annotated, Union
def f(x: Union[Annotated['int', '>0'], 'integer']) -> None:
return None
",
&[Rule::UndefinedName],
);
}
#[ignore]
#[test]
fn literal_type_some_other_module() {
flakes(
r"
from my_module import compat
from my_module.compat import Literal
def f(x: compat.Literal['some string']) -> None:
return None
def g(x: Literal['some string']) -> None:
return None
",
&[],
);
}
#[test]
fn literal_union_type_typing() {
flakes(
r"
from typing import Literal
def f(x: Literal['some string', 'foo bar']) -> None:
return None
",
&[],
);
}
#[ignore]
#[test]
fn deferred_twice_annotation() {
flakes(
r#"
from queue import Queue
from typing import Optional
def f() -> "Optional['Queue[str]']":
return None
"#,
&[],
);
}
#[test]
fn partial_string_annotations_with_future_annotations() {
flakes(
r"
from __future__ import annotations
from queue import Queue
from typing import Optional
def f() -> Optional['Queue[str]']:
return None
",
&[],
);
}
#[test]
fn forward_annotations_for_classes_in_scope() {
flakes(
r#"
from typing import Optional
def f():
class C:
a: "D"
b: Optional["D"]
c: "Optional[D]"
class D: pass
"#,
&[],
);
}
#[test]
fn idiomiatic_typing_guards() {
flakes(
r"
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from t import T
def f() -> T:
pass
",
&[],
);
flakes(
r"
if False:
from t import T
def f() -> T:
pass
",
&[],
);
flakes(
r"
MYPY = False
if MYPY:
from t import T
def f() -> T:
pass
",
&[],
);
}
#[test]
fn typing_guard_for_protocol() {
flakes(
r"
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Protocol
else:
Protocol = object
class C(Protocol):
def f() -> int:
pass
",
&[],
);
}
#[test]
fn typed_names_correct_forward_ref() {
flakes(
r#"
from typing import TypedDict, List, NamedTuple
List[TypedDict("x", {})]
List[TypedDict("x", x=int)]
List[NamedTuple("a", a=int)]
List[NamedTuple("a", [("a", int)])]
"#,
&[],
);
flakes(
r#"
from typing import TypedDict, List, NamedTuple, TypeVar
List[TypedDict("x", {"x": "Y"})]
List[TypedDict("x", x="Y")]
List[NamedTuple("a", [("a", "Y")])]
List[NamedTuple("a", a="Y")]
List[TypedDict("x", {"x": List["a"]})]
List[TypeVar("A", bound="C")]
List[TypeVar("A", List["C"])]
"#,
&[
Rule::UndefinedName,
Rule::UndefinedName,
Rule::UndefinedName,
Rule::UndefinedName,
Rule::UndefinedName,
Rule::UndefinedName,
Rule::UndefinedName,
],
);
flakes(
r#"
from typing import NamedTuple, TypeVar, cast
from t import A, B, C, D, E
NamedTuple("A", [("a", A["C"])])
TypeVar("A", bound=A["B"])
TypeVar("A", A["D"])
cast(A["E"], [])
"#,
&[],
);
flakes(
r#"
from typing import NewType
def f():
name = "x"
NewType(name, int)
"#,
&[],
);
}
#[test]
fn named_types_classes() {
flakes(
r#"
from typing import TypedDict, NamedTuple
class X(TypedDict):
y: TypedDict("z", {"zz":int})
class Y(NamedTuple):
y: NamedTuple("v", [("vv", int)])
"#,
&[],
);
}
#[test]
fn gh_issue_17196_regression_test() {
flakes(
r#"
from typing import Annotated
def type_annotations_from_tuple():
annos = (str, "foo", "bar")
return Annotated[annos]
def type_annotations_from_filtered_tuple():
annos = (str, None, "foo", None, "bar")
return Annotated[tuple([a for a in annos if a is not None])]
"#,
&[],
);
}
}