Files
ruff/src/linter.rs
2022-08-30 14:37:06 -04:00

378 lines
12 KiB
Rust

use std::path::Path;
use anyhow::Result;
use log::debug;
use rustpython_parser::parser;
use crate::check_ast::check_ast;
use crate::check_lines::check_lines;
use crate::checks::{Check, LintSource};
use crate::message::Message;
use crate::settings::Settings;
use crate::{cache, fs};
pub fn check_path(path: &Path, settings: &Settings, mode: &cache::Mode) -> Result<Vec<Message>> {
// Check the cache.
if let Some(messages) = cache::get(path, settings, mode) {
debug!("Cache hit for: {}", path.to_string_lossy());
return Ok(messages);
}
// Read the file from disk.
let contents = fs::read_file(path)?;
// Aggregate all checks.
let mut checks: Vec<Check> = vec![];
// Run the AST-based checks.
if settings
.select
.iter()
.any(|check_code| matches!(check_code.lint_source(), LintSource::AST))
{
let path = path.to_string_lossy();
let python_ast = parser::parse_program(&contents, &path)?;
checks.extend(check_ast(&python_ast, settings, &path));
}
// Run the lines-based checks.
check_lines(&mut checks, &contents, settings);
// Convert to messages.
let messages: Vec<Message> = checks
.into_iter()
.map(|check| Message {
kind: check.kind,
location: check.location,
filename: path.to_string_lossy().to_string(),
})
.collect();
cache::set(path, settings, &messages, mode);
Ok(messages)
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use std::path::Path;
use anyhow::Result;
use rustpython_parser::ast::Location;
use crate::checks::{CheckCode, CheckKind};
use crate::linter::check_path;
use crate::message::Message;
use crate::{cache, settings};
#[test]
fn e501() -> Result<()> {
let actual = check_path(
&Path::new("./resources/test/src/E501.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::E501]),
},
&cache::Mode::None,
)?;
let expected = vec![Message {
kind: CheckKind::LineTooLong,
location: Location::new(5, 89),
filename: "./resources/test/src/E501.py".to_string(),
}];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f401() -> Result<()> {
let actual = check_path(
&Path::new("./resources/test/src/F401.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F401]),
},
&cache::Mode::None,
)?;
let expected = vec![
Message {
kind: CheckKind::UnusedImport("functools".to_string()),
location: Location::new(2, 1),
filename: "./resources/test/src/F401.py".to_string(),
},
Message {
kind: CheckKind::UnusedImport("collections.OrderedDict".to_string()),
location: Location::new(3, 1),
filename: "./resources/test/src/F401.py".to_string(),
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f403() -> Result<()> {
let actual = check_path(
&Path::new("./resources/test/src/F403.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F403]),
},
&cache::Mode::None,
)?;
let expected = vec![
Message {
kind: CheckKind::ImportStarUsage,
location: Location::new(1, 1),
filename: "./resources/test/src/F403.py".to_string(),
},
Message {
kind: CheckKind::ImportStarUsage,
location: Location::new(2, 1),
filename: "./resources/test/src/F403.py".to_string(),
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f541() -> Result<()> {
let actual = check_path(
&Path::new("./resources/test/src/F541.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F541]),
},
&cache::Mode::None,
)?;
let expected = vec![
Message {
kind: CheckKind::FStringMissingPlaceholders,
location: Location::new(4, 7),
filename: "./resources/test/src/F541.py".to_string(),
},
Message {
kind: CheckKind::FStringMissingPlaceholders,
location: Location::new(5, 7),
filename: "./resources/test/src/F541.py".to_string(),
},
Message {
kind: CheckKind::FStringMissingPlaceholders,
location: Location::new(7, 7),
filename: "./resources/test/src/F541.py".to_string(),
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f634() -> Result<()> {
let actual = check_path(
&Path::new("./resources/test/src/F634.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F634]),
},
&cache::Mode::None,
)?;
let expected = vec![
Message {
kind: CheckKind::IfTuple,
location: Location::new(1, 1),
filename: "./resources/test/src/F634.py".to_string(),
},
Message {
kind: CheckKind::IfTuple,
location: Location::new(7, 5),
filename: "./resources/test/src/F634.py".to_string(),
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f706() -> Result<()> {
let actual = check_path(
&Path::new("./resources/test/src/F706.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F706]),
},
&cache::Mode::None,
)?;
let expected = vec![
Message {
kind: CheckKind::ReturnOutsideFunction,
location: Location::new(6, 5),
filename: "./resources/test/src/F706.py".to_string(),
},
Message {
kind: CheckKind::ReturnOutsideFunction,
location: Location::new(9, 1),
filename: "./resources/test/src/F706.py".to_string(),
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f821() -> Result<()> {
let actual = check_path(
&Path::new("./resources/test/src/F821.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F821]),
},
&cache::Mode::None,
)?;
let expected = vec![
Message {
kind: CheckKind::UndefinedName("self".to_string()),
location: Location::new(2, 12),
filename: "./resources/test/src/F821.py".to_string(),
},
Message {
kind: CheckKind::UndefinedName("self".to_string()),
location: Location::new(6, 13),
filename: "./resources/test/src/F821.py".to_string(),
},
Message {
kind: CheckKind::UndefinedName("self".to_string()),
location: Location::new(10, 9),
filename: "./resources/test/src/F821.py".to_string(),
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f831() -> Result<()> {
let actual = check_path(
&Path::new("./resources/test/src/F831.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F831]),
},
&cache::Mode::None,
)?;
let expected = vec![
Message {
kind: CheckKind::DuplicateArgumentName,
location: Location::new(1, 25),
filename: "./resources/test/src/F831.py".to_string(),
},
Message {
kind: CheckKind::DuplicateArgumentName,
location: Location::new(5, 28),
filename: "./resources/test/src/F831.py".to_string(),
},
Message {
kind: CheckKind::DuplicateArgumentName,
location: Location::new(9, 27),
filename: "./resources/test/src/F831.py".to_string(),
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f832() -> Result<()> {
let actual = check_path(
&Path::new("./resources/test/src/F832.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F832]),
},
&cache::Mode::None,
)?;
let expected = vec![Message {
kind: CheckKind::UndefinedLocal("my_var".to_string()),
location: Location::new(6, 5),
filename: "./resources/test/src/F832.py".to_string(),
}];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
#[test]
fn f901() -> Result<()> {
let actual = check_path(
&Path::new("./resources/test/src/F901.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F901]),
},
&cache::Mode::None,
)?;
let expected = vec![
Message {
kind: CheckKind::RaiseNotImplemented,
location: Location::new(2, 5),
filename: "./resources/test/src/F901.py".to_string(),
},
Message {
kind: CheckKind::RaiseNotImplemented,
location: Location::new(6, 5),
filename: "./resources/test/src/F901.py".to_string(),
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
}