Consider VS Code cell metadata to determine valid code cells (#12864)

## Summary

This PR adds support for VS Code specific cell metadata to consider when
collecting valid code cells.

For context, Ruff only runs on valid code cells. These are the code
cells that doesn't contain cell magics. Previously, Ruff only used the
notebook's metadata to determine whether it's a Python notebook. But, in
VS Code, a notebook's preferred language might be Python but it could
still contain code cells for other languages. This can be determined
with the `metadata.vscode.languageId` field.

### References:
* https://code.visualstudio.com/docs/languages/identifiers
* e6c009a3d4/extensions/ipynb/src/serializers.ts (L104-L107)
*
e6c009a3d4/extensions/ipynb/src/serializers.ts (L117-L122)

This brings us one step closer to fixing #12281.

## Test Plan

Add test cases for `is_valid_python_code_cell` and an integration test
case which showcase running it end to end. The test notebook contains a
JavaScript code cell and a Python code cell.
This commit is contained in:
Dhruv Manilawala 2024-08-13 22:09:56 +05:30 committed by GitHub
parent 899a52390b
commit ff53db3d99
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 226 additions and 21 deletions

View file

@ -19,7 +19,7 @@ use ruff_text_size::TextSize;
use crate::cell::CellOffsets;
use crate::index::NotebookIndex;
use crate::schema::{Cell, RawNotebook, SortAlphabetically, SourceValue};
use crate::{schema, RawNotebookMetadata};
use crate::{schema, CellMetadata, RawNotebookMetadata};
/// Run round-trip source code generation on a given Jupyter notebook file path.
pub fn round_trip(path: &Path) -> anyhow::Result<String> {
@ -131,7 +131,7 @@ impl Notebook {
.cells
.iter()
.enumerate()
.filter(|(_, cell)| cell.is_valid_code_cell())
.filter(|(_, cell)| cell.is_valid_python_code_cell())
.map(|(cell_index, _)| u32::try_from(cell_index).unwrap())
.collect::<Vec<_>>();
@ -205,16 +205,14 @@ impl Notebook {
})
}
/// Creates an empty notebook.
///
///
/// Creates an empty notebook with a single code cell.
pub fn empty() -> Self {
Self::from_raw_notebook(
RawNotebook {
cells: vec![schema::Cell::Code(schema::CodeCell {
execution_count: None,
id: None,
metadata: serde_json::Value::default(),
metadata: CellMetadata::default(),
outputs: vec![],
source: schema::SourceValue::String(String::default()),
})],
@ -507,7 +505,9 @@ mod tests {
#[test_case("automagic_before_code", false)]
#[test_case("automagic_after_code", true)]
#[test_case("unicode_magic_gh9145", true)]
fn test_is_valid_code_cell(cell: &str, expected: bool) -> Result<()> {
#[test_case("vscode_language_id_python", true)]
#[test_case("vscode_language_id_javascript", false)]
fn test_is_valid_python_code_cell(cell: &str, expected: bool) -> Result<()> {
/// Read a Jupyter cell from the `resources/test/fixtures/jupyter/cell` directory.
fn read_jupyter_cell(path: impl AsRef<Path>) -> Result<Cell> {
let path = notebook_path("cell").join(path);
@ -516,7 +516,7 @@ mod tests {
}
assert_eq!(
read_jupyter_cell(format!("{cell}.json"))?.is_valid_code_cell(),
read_jupyter_cell(format!("{cell}.json"))?.is_valid_python_code_cell(),
expected
);
Ok(())
@ -596,4 +596,12 @@ print("after empty cells")
);
Ok(())
}
#[test]
fn round_trip() {
let path = notebook_path("vscode_language_id.ipynb");
let expected = std::fs::read_to_string(&path).unwrap();
let actual = super::round_trip(&path).unwrap();
assert_eq!(actual, expected);
}
}