Skip to content

Commit 7bc83d9

Browse files
committed
Formatter: Fix syntax error location in notebooks
1 parent 37fbe58 commit 7bc83d9

File tree

4 files changed

+90
-2
lines changed

4 files changed

+90
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ruff/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ ruff_macros = { workspace = true }
2222
ruff_notebook = { workspace = true }
2323
ruff_python_ast = { workspace = true }
2424
ruff_python_formatter = { workspace = true }
25+
ruff_python_parser = { workspace = true }
2526
ruff_server = { workspace = true }
2627
ruff_source_file = { workspace = true }
2728
ruff_text_size = { workspace = true }

crates/ruff/src/commands/format.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use itertools::Itertools;
1111
use log::{error, warn};
1212
use rayon::iter::Either::{Left, Right};
1313
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
14+
use ruff_python_parser::ParseError;
1415
use rustc_hash::FxHashSet;
1516
use thiserror::Error;
1617
use tracing::debug;
@@ -26,7 +27,7 @@ use ruff_linter::source_kind::{SourceError, SourceKind};
2627
use ruff_linter::warn_user_once;
2728
use ruff_python_ast::{PySourceType, SourceType};
2829
use ruff_python_formatter::{format_module_source, format_range, FormatModuleError, QuoteStyle};
29-
use ruff_source_file::LineIndex;
30+
use ruff_source_file::{LineIndex, SourceCode};
3031
use ruff_text_size::{TextLen, TextRange, TextSize};
3132
use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile, Resolver};
3233
use ruff_workspace::FormatterSettings;
@@ -406,8 +407,12 @@ pub(crate) fn format_source(
406407
let formatted =
407408
format_module_source(unformatted, options.clone()).map_err(|err| {
408409
if let FormatModuleError::ParseError(err) = err {
410+
// Offset the error by the start of the cell
409411
DisplayParseError::from_source_kind(
410-
err,
412+
ParseError {
413+
error: err.error,
414+
location: err.location.checked_add(*start).unwrap(),
415+
},
411416
path.map(Path::to_path_buf),
412417
source_kind,
413418
)

crates/ruff/tests/format.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,6 +1699,87 @@ fn test_notebook_trailing_semicolon() {
16991699
"##);
17001700
}
17011701

1702+
#[test]
1703+
fn syntax_error_in_notebooks() -> Result<()> {
1704+
let tempdir = TempDir::new()?;
1705+
1706+
let ruff_toml = tempdir.path().join("ruff.toml");
1707+
fs::write(
1708+
&ruff_toml,
1709+
r#"
1710+
include = ["*.ipy"]
1711+
"#,
1712+
)?;
1713+
1714+
fs::write(
1715+
tempdir.path().join("main.ipy"),
1716+
r#"
1717+
{
1718+
"cells": [
1719+
{
1720+
"cell_type": "code",
1721+
"execution_count": null,
1722+
"metadata": {
1723+
"id": "S6nTuMqGGqp2"
1724+
},
1725+
"outputs": [],
1726+
"source": [
1727+
"np.random.seed(RANDOM_STATE)\n",
1728+
"X = pd.DataFrame(data=X, columns=np.arange(0, X.shape[1]))\n",
1729+
"X[10] = X[6] + X[7] + np.random.random() * 0.01"
1730+
]
1731+
},
1732+
{
1733+
"cell_type": "code",
1734+
"execution_count": null,
1735+
"metadata": {
1736+
"id": "fTZWxz1zpb9R"
1737+
},
1738+
"outputs": [],
1739+
"source": [
1740+
"for i in range(iterations):\n",
1741+
" # выберите случайный индекс в диапазон от 0 до len(X)-1 включительно при помощи функции random.randint\n",
1742+
" j = # ваш код здесь\n"
1743+
]
1744+
}
1745+
],
1746+
"metadata": {
1747+
"colab": {
1748+
"provenance": []
1749+
},
1750+
"kernelspec": {
1751+
"display_name": "ml",
1752+
"language": "python",
1753+
"name": "python3"
1754+
},
1755+
"language_info": {
1756+
"name": "python",
1757+
"version": "3.12.9"
1758+
}
1759+
},
1760+
"nbformat": 4,
1761+
"nbformat_minor": 0
1762+
}
1763+
"#,
1764+
)?;
1765+
1766+
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
1767+
.current_dir(tempdir.path())
1768+
.arg("format")
1769+
.arg("--no-cache")
1770+
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
1771+
.args(["--extension", "ipy:ipynb"])
1772+
.arg("."), @r"
1773+
success: false
1774+
exit_code: 2
1775+
----- stdout -----
1776+
1777+
----- stderr -----
1778+
error: Failed to parse main.ipy:2:3:24: Expected an expression
1779+
");
1780+
Ok(())
1781+
}
1782+
17021783
#[test]
17031784
fn extension() -> Result<()> {
17041785
let tempdir = TempDir::new()?;

0 commit comments

Comments
 (0)