Skip to content

Commit

Permalink
Track top-level module imports in the semantic model
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Feb 2, 2024
1 parent 92d99a7 commit 76203ea
Show file tree
Hide file tree
Showing 27 changed files with 197 additions and 36 deletions.
37 changes: 25 additions & 12 deletions crates/ruff_linter/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,13 +349,6 @@ where
}
}

// Track each top-level import, to guide import insertions.
if matches!(stmt, Stmt::Import(_) | Stmt::ImportFrom(_)) {
if self.semantic.at_top_level() {
self.importer.visit_import(stmt);
}
}

// Store the flags prior to any further descent, so that we can restore them after visiting
// the node.
let flags_snapshot = self.semantic.flags;
Expand All @@ -371,14 +364,22 @@ where
self.handle_node_load(target);
}
Stmt::Import(ast::StmtImport { names, range: _ }) => {
if self.semantic.at_top_level() {
self.importer.visit_import(stmt);
}

for alias in names {
if alias.name.contains('.') && alias.asname.is_none() {
// Given `import foo.bar`, `name` would be "foo", and `qualified_name` would be
// "foo.bar".
let name = alias.name.split('.').next().unwrap();
// Given `import foo.bar`, `module` would be "foo", and `call_path` would be
// `["foo", "bar"]`.
let module = alias.name.split('.').next().unwrap();

// Mark the top-level module as "seen" by the semantic model.
self.semantic.see(module);

if alias.asname.is_none() && alias.name.contains('.') {
let call_path: Box<[&str]> = alias.name.split('.').collect();
self.add_binding(
name,
module,
alias.identifier(),
BindingKind::SubmoduleImport(SubmoduleImport { call_path }),
BindingFlags::EXTERNAL,
Expand Down Expand Up @@ -413,8 +414,20 @@ where
level,
range: _,
}) => {
if self.semantic.at_top_level() {
self.importer.visit_import(stmt);
}

let module = module.as_deref();
let level = *level;

// Mark the top-level module as "seen" by the semantic model.
if level.map_or(true, |level| level == 0) {
if let Some(module) = module.and_then(|module| module.split('.').next()) {
self.semantic.see(module);
}
}

for alias in names {
if let Some("__future__") = module {
let name = alias.asname.as_ref().unwrap_or(&alias.name);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use ruff_python_ast::Expr;

use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::Expr;
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -47,6 +47,10 @@ impl Violation for SixPY3 {

/// YTT202
pub(crate) fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
if !checker.semantic().seen(Modules::SIX) {
return;
}

if checker
.semantic()
.resolve_call_path(expr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::Violation;
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast};
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;

/// ## What it does
Expand Down Expand Up @@ -48,6 +49,10 @@ impl Violation for TarfileUnsafeMembers {

/// S202
pub(crate) fn tarfile_unsafe_members(checker: &mut Checker, call: &ast::ExprCall) {
if !checker.semantic().seen(Modules::TARFILE) {
return;
}

if !call
.func
.as_attribute_expr()
Expand All @@ -65,10 +70,6 @@ pub(crate) fn tarfile_unsafe_members(checker: &mut Checker, call: &ast::ExprCall
return;
}

if !checker.semantic().seen(&["tarfile"]) {
return;
}

checker
.diagnostics
.push(Diagnostic::new(TarfileUnsafeMembers, call.func.range()));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -48,6 +49,10 @@ impl Violation for DjangoAllWithModelForm {

/// DJ007
pub(crate) fn all_with_model_form(checker: &mut Checker, class_def: &ast::StmtClassDef) {
if !checker.semantic().seen(Modules::DJANGO) {
return;
}

if !is_model_form(class_def, checker.semantic()) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -46,6 +47,10 @@ impl Violation for DjangoExcludeWithModelForm {

/// DJ006
pub(crate) fn exclude_with_model_form(checker: &mut Checker, class_def: &ast::StmtClassDef) {
if !checker.semantic().seen(Modules::DJANGO) {
return;
}

if !is_model_form(class_def, checker.semantic()) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::SemanticModel;
use ruff_python_semantic::{Modules, SemanticModel};
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -45,6 +45,10 @@ impl Violation for DjangoLocalsInRenderFunction {

/// DJ003
pub(crate) fn locals_in_render_function(checker: &mut Checker, call: &ast::ExprCall) {
if !checker.semantic().seen(Modules::DJANGO) {
return;
}

if !checker
.semantic()
.resolve_call_path(&call.func)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_const_true;
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_semantic::{analyze, SemanticModel};
use ruff_python_semantic::{analyze, Modules, SemanticModel};

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

Expand Down Expand Up @@ -52,6 +52,10 @@ impl Violation for DjangoModelWithoutDunderStr {

/// DJ008
pub(crate) fn model_without_dunder_str(checker: &mut Checker, class_def: &ast::StmtClassDef) {
if !checker.semantic().seen(Modules::DJANGO) {
return;
}

if !is_non_abstract_model(class_def, checker.semantic()) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use ruff_python_ast::Decorator;

use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -51,6 +52,10 @@ impl Violation for DjangoNonLeadingReceiverDecorator {

/// DJ013
pub(crate) fn non_leading_receiver_decorator(checker: &mut Checker, decorator_list: &[Decorator]) {
if !checker.semantic().seen(Modules::DJANGO) {
return;
}

let mut seen_receiver = false;
for (i, decorator) in decorator_list.iter().enumerate() {
let is_receiver = decorator.expression.as_call_expr().is_some_and(|call| {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_const_true;
use ruff_python_semantic::SemanticModel;
use ruff_python_semantic::{Modules, SemanticModel};
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -55,6 +55,10 @@ impl Violation for DjangoNullableModelStringField {

/// DJ001
pub(crate) fn nullable_model_string_field(checker: &mut Checker, body: &[Stmt]) {
if !checker.semantic().seen(Modules::DJANGO) {
return;
}

for statement in body {
let Stmt::Assign(ast::StmtAssign { value, .. }) = statement else {
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fmt;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_semantic::SemanticModel;
use ruff_python_semantic::{Modules, SemanticModel};
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -83,6 +83,10 @@ pub(crate) fn unordered_body_content_in_model(
checker: &mut Checker,
class_def: &ast::StmtClassDef,
) {
if !checker.semantic().seen(Modules::DJANGO) {
return;
}

if !helpers::is_model(class_def, checker.semantic()) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast as ast;
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -54,6 +55,10 @@ impl Violation for DirectLoggerInstantiation {

/// LOG001
pub(crate) fn direct_logger_instantiation(checker: &mut Checker, call: &ast::ExprCall) {
if !checker.semantic().seen(Modules::LOGGING) {
return;
}

if checker
.semantic()
.resolve_call_path(call.func.as_ref())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -57,6 +58,10 @@ impl Violation for InvalidGetLoggerArgument {

/// LOG002
pub(crate) fn invalid_get_logger_argument(checker: &mut Checker, call: &ast::ExprCall) {
if !checker.semantic().seen(Modules::LOGGING) {
return;
}

let Some(Expr::Name(expr @ ast::ExprName { id, .. })) = call.arguments.find_argument("name", 0)
else {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use ruff_python_ast::Expr;

use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -48,6 +49,10 @@ impl Violation for UndocumentedWarn {

/// LOG009
pub(crate) fn undocumented_warn(checker: &mut Checker, expr: &Expr) {
if !checker.semantic().seen(Modules::LOGGING) {
return;
}

if checker
.semantic()
.resolve_call_path(expr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use ruff_python_ast::Expr;

use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -50,6 +51,10 @@ impl Violation for CollectionsNamedTuple {

/// PYI024
pub(crate) fn collections_named_tuple(checker: &mut Checker, expr: &Expr) {
if !checker.semantic().seen(Modules::COLLECTIONS) {
return;
}

if checker
.semantic()
.resolve_call_path(expr)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast as ast;
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -48,15 +49,17 @@ pub(crate) fn async_function_with_timeout(
if !function_def.is_async {
return;
}
let Some(timeout) = function_def.parameters.find("timeout") else {
return;
};

// If `trio` isn't in scope, avoid raising the diagnostic.
if !checker.semantic().seen(&["trio"]) {
if !checker.semantic().seen(Modules::TRIO) {
return;
}

// If the function doesn't have a `timeout` parameter, avoid raising the diagnostic.
let Some(timeout) = function_def.parameters.find("timeout") else {
return;
};

checker.diagnostics.push(Diagnostic::new(
TrioAsyncFunctionWithTimeout,
timeout.range(),
Expand Down
5 changes: 5 additions & 0 deletions crates/ruff_linter/src/rules/flake8_trio/rules/sync_call.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{Expr, ExprCall};
use ruff_python_semantic::Modules;
use ruff_text_size::{Ranged, TextRange};

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -51,6 +52,10 @@ impl Violation for TrioSyncCall {

/// TRIO105
pub(crate) fn sync_call(checker: &mut Checker, call: &ExprCall) {
if !checker.semantic().seen(Modules::TRIO) {
return;
}

let Some(method_name) = ({
let Some(call_path) = checker.semantic().resolve_call_path(call.func.as_ref()) else {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::AwaitVisitor;
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{StmtWith, WithItem};
use ruff_python_semantic::Modules;

use crate::checkers::ast::Checker;
use crate::rules::flake8_trio::method_name::MethodName;
Expand Down Expand Up @@ -49,6 +50,10 @@ pub(crate) fn timeout_without_await(
with_stmt: &StmtWith,
with_items: &[WithItem],
) {
if !checker.semantic().seen(Modules::TRIO) {
return;
}

let Some(method_name) = with_items.iter().find_map(|item| {
let call = item.context_expr.as_call_expr()?;
let call_path = checker.semantic().resolve_call_path(call.func.as_ref())?;
Expand Down
Loading

0 comments on commit 76203ea

Please sign in to comment.