mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:37 +00:00
[red-knot] Add filename and source location for diagnostics (#12842)
## Summary I'm not sure if this is useful but this is a hacky implementation to add the filename and row / column numbers to the current Red Knot diagnostics.
This commit is contained in:
parent
540023262e
commit
99dc208b00
6 changed files with 74 additions and 12 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1900,6 +1900,7 @@ dependencies = [
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
"ruff_python_parser",
|
"ruff_python_parser",
|
||||||
"ruff_python_stdlib",
|
"ruff_python_stdlib",
|
||||||
|
"ruff_source_file",
|
||||||
"ruff_text_size",
|
"ruff_text_size",
|
||||||
"rustc-hash 2.0.0",
|
"rustc-hash 2.0.0",
|
||||||
"salsa",
|
"salsa",
|
||||||
|
@ -1962,6 +1963,7 @@ dependencies = [
|
||||||
"ruff_cache",
|
"ruff_cache",
|
||||||
"ruff_db",
|
"ruff_db",
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
|
"ruff_text_size",
|
||||||
"rustc-hash 2.0.0",
|
"rustc-hash 2.0.0",
|
||||||
"salsa",
|
"salsa",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
|
@ -15,6 +15,7 @@ ruff_db = { workspace = true }
|
||||||
ruff_index = { workspace = true }
|
ruff_index = { workspace = true }
|
||||||
ruff_python_ast = { workspace = true }
|
ruff_python_ast = { workspace = true }
|
||||||
ruff_python_stdlib = { workspace = true }
|
ruff_python_stdlib = { workspace = true }
|
||||||
|
ruff_source_file = { workspace = true }
|
||||||
ruff_text_size = { workspace = true }
|
ruff_text_size = { workspace = true }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::{File, FilePath};
|
||||||
|
use ruff_db::source::line_index;
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
use ruff_python_ast::{Expr, ExpressionRef, StmtClassDef};
|
use ruff_python_ast::{Expr, ExpressionRef, StmtClassDef};
|
||||||
|
use ruff_source_file::LineIndex;
|
||||||
|
|
||||||
use crate::module_name::ModuleName;
|
use crate::module_name::ModuleName;
|
||||||
use crate::module_resolver::{resolve_module, Module};
|
use crate::module_resolver::{resolve_module, Module};
|
||||||
|
@ -25,6 +27,14 @@ impl<'db> SemanticModel<'db> {
|
||||||
self.db
|
self.db
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn file_path(&self) -> &FilePath {
|
||||||
|
self.file.path(self.db)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line_index(&self) -> LineIndex {
|
||||||
|
line_index(self.db.upcast(), self.file)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resolve_module(&self, module_name: ModuleName) -> Option<Module> {
|
pub fn resolve_module(&self, module_name: ModuleName) -> Option<Module> {
|
||||||
resolve_module(self.db, module_name)
|
resolve_module(self.db, module_name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,5 +17,5 @@ fn check() {
|
||||||
|
|
||||||
let result = workspace.check_file(&test).expect("Check to succeed");
|
let result = workspace.check_file(&test).expect("Check to succeed");
|
||||||
|
|
||||||
assert_eq!(result, vec!["Unresolved import 'random22'"]);
|
assert_eq!(result, vec!["/test.py:1:8: Unresolved import 'random22'"]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ red_knot_python_semantic = { workspace = true }
|
||||||
ruff_cache = { workspace = true }
|
ruff_cache = { workspace = true }
|
||||||
ruff_db = { workspace = true, features = ["os", "cache"] }
|
ruff_db = { workspace = true, features = ["os", "cache"] }
|
||||||
ruff_python_ast = { workspace = true }
|
ruff_python_ast = { workspace = true }
|
||||||
|
ruff_text_size = { workspace = true }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
crossbeam = { workspace = true }
|
crossbeam = { workspace = true }
|
||||||
|
|
|
@ -8,9 +8,10 @@ use red_knot_python_semantic::types::Type;
|
||||||
use red_knot_python_semantic::{HasTy, ModuleName, SemanticModel};
|
use red_knot_python_semantic::{HasTy, ModuleName, SemanticModel};
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::{parsed_module, ParsedModule};
|
use ruff_db::parsed::{parsed_module, ParsedModule};
|
||||||
use ruff_db::source::{source_text, SourceText};
|
use ruff_db::source::{line_index, source_text, SourceText};
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
use ruff_python_ast::visitor::{walk_expr, walk_stmt, Visitor};
|
use ruff_python_ast::visitor::{walk_expr, walk_stmt, Visitor};
|
||||||
|
use ruff_text_size::{Ranged, TextSize};
|
||||||
|
|
||||||
use crate::db::Db;
|
use crate::db::Db;
|
||||||
|
|
||||||
|
@ -49,7 +50,18 @@ pub(crate) fn lint_syntax(db: &dyn Db, file_id: File) -> Diagnostics {
|
||||||
visitor.visit_body(&ast.body);
|
visitor.visit_body(&ast.body);
|
||||||
diagnostics = visitor.diagnostics;
|
diagnostics = visitor.diagnostics;
|
||||||
} else {
|
} else {
|
||||||
diagnostics.extend(parsed.errors().iter().map(ToString::to_string));
|
let path = file_id.path(db);
|
||||||
|
let line_index = line_index(db.upcast(), file_id);
|
||||||
|
diagnostics.extend(parsed.errors().iter().map(|err| {
|
||||||
|
let source_location = line_index.source_location(err.location.start(), source.as_str());
|
||||||
|
format!(
|
||||||
|
"{}:{}:{}: {}",
|
||||||
|
path.as_str(),
|
||||||
|
source_location.row,
|
||||||
|
source_location.column,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Diagnostics::from(diagnostics)
|
Diagnostics::from(diagnostics)
|
||||||
|
@ -97,6 +109,20 @@ pub fn lint_semantic(db: &dyn Db, file_id: File) -> Diagnostics {
|
||||||
Diagnostics::from(context.diagnostics.take())
|
Diagnostics::from(context.diagnostics.take())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_diagnostic(context: &SemanticLintContext, message: &str, start: TextSize) -> String {
|
||||||
|
let source_location = context
|
||||||
|
.semantic
|
||||||
|
.line_index()
|
||||||
|
.source_location(start, context.source_text());
|
||||||
|
format!(
|
||||||
|
"{}:{}:{}: {}",
|
||||||
|
context.semantic.file_path().as_str(),
|
||||||
|
source_location.row,
|
||||||
|
source_location.column,
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn lint_unresolved_imports(context: &SemanticLintContext, import: AnyImportRef) {
|
fn lint_unresolved_imports(context: &SemanticLintContext, import: AnyImportRef) {
|
||||||
match import {
|
match import {
|
||||||
AnyImportRef::Import(import) => {
|
AnyImportRef::Import(import) => {
|
||||||
|
@ -104,7 +130,11 @@ fn lint_unresolved_imports(context: &SemanticLintContext, import: AnyImportRef)
|
||||||
let ty = alias.ty(&context.semantic);
|
let ty = alias.ty(&context.semantic);
|
||||||
|
|
||||||
if ty.is_unbound() {
|
if ty.is_unbound() {
|
||||||
context.push_diagnostic(format!("Unresolved import '{}'", &alias.name));
|
context.push_diagnostic(format_diagnostic(
|
||||||
|
context,
|
||||||
|
&format!("Unresolved import '{}'", &alias.name),
|
||||||
|
alias.start(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,7 +143,11 @@ fn lint_unresolved_imports(context: &SemanticLintContext, import: AnyImportRef)
|
||||||
let ty = alias.ty(&context.semantic);
|
let ty = alias.ty(&context.semantic);
|
||||||
|
|
||||||
if ty.is_unbound() {
|
if ty.is_unbound() {
|
||||||
context.push_diagnostic(format!("Unresolved import '{}'", &alias.name));
|
context.push_diagnostic(format_diagnostic(
|
||||||
|
context,
|
||||||
|
&format!("Unresolved import '{}'", &alias.name),
|
||||||
|
alias.start(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,12 +161,17 @@ fn lint_maybe_undefined(context: &SemanticLintContext, name: &ast::ExprName) {
|
||||||
let semantic = &context.semantic;
|
let semantic = &context.semantic;
|
||||||
match name.ty(semantic) {
|
match name.ty(semantic) {
|
||||||
Type::Unbound => {
|
Type::Unbound => {
|
||||||
context.push_diagnostic(format!("Name '{}' used when not defined.", &name.id));
|
context.push_diagnostic(format_diagnostic(
|
||||||
|
context,
|
||||||
|
&format!("Name '{}' used when not defined.", &name.id),
|
||||||
|
name.start(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Type::Union(union) if union.contains(semantic.db(), Type::Unbound) => {
|
Type::Union(union) if union.contains(semantic.db(), Type::Unbound) => {
|
||||||
context.push_diagnostic(format!(
|
context.push_diagnostic(format_diagnostic(
|
||||||
"Name '{}' used when possibly not defined.",
|
context,
|
||||||
&name.id
|
&format!("Name '{}' used when possibly not defined.", &name.id),
|
||||||
|
name.start(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -303,6 +342,15 @@ enum AnyImportRef<'a> {
|
||||||
ImportFrom(&'a ast::StmtImportFrom),
|
ImportFrom(&'a ast::StmtImportFrom),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Ranged for AnyImportRef<'_> {
|
||||||
|
fn range(&self) -> ruff_text_size::TextRange {
|
||||||
|
match self {
|
||||||
|
AnyImportRef::Import(import) => import.range(),
|
||||||
|
AnyImportRef::ImportFrom(import) => import.range(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use red_knot_python_semantic::{Program, ProgramSettings, PythonVersion, SearchPathSettings};
|
use red_knot_python_semantic::{Program, ProgramSettings, PythonVersion, SearchPathSettings};
|
||||||
|
@ -364,8 +412,8 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*messages,
|
*messages,
|
||||||
vec![
|
vec![
|
||||||
"Name 'flag' used when not defined.",
|
"/src/a.py:3:4: Name 'flag' used when not defined.",
|
||||||
"Name 'y' used when possibly not defined."
|
"/src/a.py:5:1: Name 'y' used when possibly not defined."
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue