Skip to content

Commit 6ef5221

Browse files
Check pyproject.toml correctly when it is passed via stdin (#16971)
## Summary Resolves #16950 and [a 1.5-year-old TODO comment](https://github.com/astral-sh/ruff/blame/8d16a5c8c98089b0d1be3e3fd68954be62dd2598/crates/ruff/src/diagnostics.rs#L380). After this change, a `pyproject.toml` will be linted the same as any Python files would when passed via stdin. ## Test Plan Integration tests.
1 parent b9a7328 commit 6ef5221

File tree

2 files changed

+220
-5
lines changed

2 files changed

+220
-5
lines changed

crates/ruff/src/diagnostics.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -367,8 +367,7 @@ pub(crate) fn lint_path(
367367
})
368368
}
369369

370-
/// Generate `Diagnostic`s from source code content derived from
371-
/// stdin.
370+
/// Generate `Diagnostic`s from source code content derived from stdin.
372371
pub(crate) fn lint_stdin(
373372
path: Option<&Path>,
374373
package: Option<PackageRoot<'_>>,
@@ -377,13 +376,37 @@ pub(crate) fn lint_stdin(
377376
noqa: flags::Noqa,
378377
fix_mode: flags::FixMode,
379378
) -> Result<Diagnostics> {
380-
// TODO(charlie): Support `pyproject.toml`.
381379
let source_type = match path.and_then(|path| settings.linter.extension.get(path)) {
382380
None => match path.map(SourceType::from).unwrap_or_default() {
383381
SourceType::Python(source_type) => source_type,
384-
SourceType::Toml(_) => {
385-
return Ok(Diagnostics::default());
382+
383+
SourceType::Toml(source_type) if source_type.is_pyproject() => {
384+
if !settings
385+
.linter
386+
.rules
387+
.iter_enabled()
388+
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
389+
{
390+
return Ok(Diagnostics::default());
391+
}
392+
393+
let path = path.unwrap();
394+
let source_file =
395+
SourceFileBuilder::new(path.to_string_lossy(), contents.clone()).finish();
396+
397+
match fix_mode {
398+
flags::FixMode::Diff | flags::FixMode::Generate => {}
399+
flags::FixMode::Apply => write!(&mut io::stdout().lock(), "{contents}")?,
400+
}
401+
402+
return Ok(Diagnostics {
403+
messages: lint_pyproject_toml(source_file, &settings.linter),
404+
fixed: FixMap::from_iter([(fs::relativize_path(path), FixTable::default())]),
405+
notebook_indexes: FxHashMap::default(),
406+
});
386407
}
408+
409+
SourceType::Toml(_) => return Ok(Diagnostics::default()),
387410
},
388411
Some(language) => PySourceType::from(language),
389412
};

crates/ruff/tests/integration_test.rs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2233,3 +2233,195 @@ unfixable = ["RUF"]
22332233

22342234
Ok(())
22352235
}
2236+
2237+
#[test]
2238+
fn pyproject_toml_stdin_syntax_error() {
2239+
let mut cmd = RuffCheck::default()
2240+
.args(["--stdin-filename", "pyproject.toml", "--select", "RUF200"])
2241+
.build();
2242+
2243+
assert_cmd_snapshot!(
2244+
cmd.pass_stdin("[project"),
2245+
@r"
2246+
success: false
2247+
exit_code: 1
2248+
----- stdout -----
2249+
pyproject.toml:1:9: RUF200 Failed to parse pyproject.toml: invalid table header
2250+
expected `.`, `]`
2251+
|
2252+
1 | [project
2253+
| ^ RUF200
2254+
|
2255+
2256+
Found 1 error.
2257+
2258+
----- stderr -----
2259+
"
2260+
);
2261+
}
2262+
2263+
#[test]
2264+
fn pyproject_toml_stdin_schema_error() {
2265+
let mut cmd = RuffCheck::default()
2266+
.args(["--stdin-filename", "pyproject.toml", "--select", "RUF200"])
2267+
.build();
2268+
2269+
assert_cmd_snapshot!(
2270+
cmd.pass_stdin("[project]\nname = 1"),
2271+
@r"
2272+
success: false
2273+
exit_code: 1
2274+
----- stdout -----
2275+
pyproject.toml:2:8: RUF200 Failed to parse pyproject.toml: invalid type: integer `1`, expected a string
2276+
|
2277+
1 | [project]
2278+
2 | name = 1
2279+
| ^ RUF200
2280+
|
2281+
2282+
Found 1 error.
2283+
2284+
----- stderr -----
2285+
"
2286+
);
2287+
}
2288+
2289+
#[test]
2290+
fn pyproject_toml_stdin_no_applicable_rules_selected() {
2291+
let mut cmd = RuffCheck::default()
2292+
.args(["--stdin-filename", "pyproject.toml"])
2293+
.build();
2294+
2295+
assert_cmd_snapshot!(
2296+
cmd.pass_stdin("[project"),
2297+
@r"
2298+
success: true
2299+
exit_code: 0
2300+
----- stdout -----
2301+
All checks passed!
2302+
2303+
----- stderr -----
2304+
"
2305+
);
2306+
}
2307+
2308+
#[test]
2309+
fn pyproject_toml_stdin_no_applicable_rules_selected_2() {
2310+
let mut cmd = RuffCheck::default()
2311+
.args(["--stdin-filename", "pyproject.toml", "--select", "F401"])
2312+
.build();
2313+
2314+
assert_cmd_snapshot!(
2315+
cmd.pass_stdin("[project"),
2316+
@r"
2317+
success: true
2318+
exit_code: 0
2319+
----- stdout -----
2320+
All checks passed!
2321+
2322+
----- stderr -----
2323+
"
2324+
);
2325+
}
2326+
2327+
#[test]
2328+
fn pyproject_toml_stdin_no_errors() {
2329+
let mut cmd = RuffCheck::default()
2330+
.args(["--stdin-filename", "pyproject.toml"])
2331+
.build();
2332+
2333+
assert_cmd_snapshot!(
2334+
cmd.pass_stdin(r#"[project]\nname = "ruff"\nversion = "0.0.0""#),
2335+
@r"
2336+
success: true
2337+
exit_code: 0
2338+
----- stdout -----
2339+
All checks passed!
2340+
2341+
----- stderr -----
2342+
"
2343+
);
2344+
}
2345+
2346+
#[test]
2347+
fn pyproject_toml_stdin_schema_error_fix() {
2348+
let mut cmd = RuffCheck::default()
2349+
.args([
2350+
"--stdin-filename",
2351+
"pyproject.toml",
2352+
"--select",
2353+
"RUF200",
2354+
"--fix",
2355+
])
2356+
.build();
2357+
2358+
assert_cmd_snapshot!(
2359+
cmd.pass_stdin("[project]\nname = 1"),
2360+
@r"
2361+
success: false
2362+
exit_code: 1
2363+
----- stdout -----
2364+
[project]
2365+
name = 1
2366+
----- stderr -----
2367+
pyproject.toml:2:8: RUF200 Failed to parse pyproject.toml: invalid type: integer `1`, expected a string
2368+
|
2369+
1 | [project]
2370+
2 | name = 1
2371+
| ^ RUF200
2372+
|
2373+
2374+
Found 1 error.
2375+
"
2376+
);
2377+
}
2378+
2379+
#[test]
2380+
fn pyproject_toml_stdin_schema_error_fix_only() {
2381+
let mut cmd = RuffCheck::default()
2382+
.args([
2383+
"--stdin-filename",
2384+
"pyproject.toml",
2385+
"--select",
2386+
"RUF200",
2387+
"--fix-only",
2388+
])
2389+
.build();
2390+
2391+
assert_cmd_snapshot!(
2392+
cmd.pass_stdin("[project]\nname = 1"),
2393+
@r"
2394+
success: true
2395+
exit_code: 0
2396+
----- stdout -----
2397+
[project]
2398+
name = 1
2399+
----- stderr -----
2400+
"
2401+
);
2402+
}
2403+
2404+
#[test]
2405+
fn pyproject_toml_stdin_schema_error_fix_diff() {
2406+
let mut cmd = RuffCheck::default()
2407+
.args([
2408+
"--stdin-filename",
2409+
"pyproject.toml",
2410+
"--select",
2411+
"RUF200",
2412+
"--fix",
2413+
"--diff",
2414+
])
2415+
.build();
2416+
2417+
assert_cmd_snapshot!(
2418+
cmd.pass_stdin("[project]\nname = 1"),
2419+
@r"
2420+
success: true
2421+
exit_code: 0
2422+
----- stdout -----
2423+
2424+
----- stderr -----
2425+
"
2426+
);
2427+
}

0 commit comments

Comments
 (0)