Skip to content

Commit 9b48c0e

Browse files
authored
fix: handle source property that's a string literal (#2)
1 parent c842992 commit 9b48c0e

File tree

2 files changed

+95
-4
lines changed

2 files changed

+95
-4
lines changed

src/format_text.rs

+47-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,14 @@ pub fn format_text(
2828
return Ok(None);
2929
};
3030

31-
Ok(format_root(input_text, &root_value, format_with_host))
31+
Ok(match format_root(input_text, &root_value, format_with_host) {
32+
Some(text) => {
33+
#[cfg(debug_assertions)]
34+
validate_output_json(&text)?;
35+
Some(text)
36+
}
37+
None => None,
38+
})
3239
}
3340

3441
fn format_root(
@@ -53,6 +60,29 @@ fn format_root(
5360
}
5461
}
5562

63+
#[cfg(debug_assertions)]
64+
fn validate_output_json(text: &str) -> Result<()> {
65+
// ensures the output is correct in debug mode
66+
let result = jsonc_parser::parse_to_ast(
67+
text,
68+
&CollectOptions {
69+
comments: false,
70+
tokens: false,
71+
},
72+
&ParseOptions {
73+
allow_comments: true,
74+
allow_loose_object_property_names: false,
75+
allow_trailing_commas: true,
76+
},
77+
);
78+
match result {
79+
Ok(_) => Ok(()),
80+
Err(err) => {
81+
anyhow::bail!("dprint-plugin-jupyter produced invalid json. Please open an issue with reproduction steps at https://github.com/dprint/dprint-plugin-jupyter/issues\n{:#}\n\n== TEXT ==\n{}", err, text);
82+
}
83+
}
84+
}
85+
5686
fn get_cell_text_change(
5787
file_text: &str,
5888
cell: &jsonc_parser::ast::Value,
@@ -74,7 +104,11 @@ fn get_cell_text_change(
74104
// many plugins will add a final newline, but that doesn't look nice in notebooks, so trim it off
75105
let formatted_text = formatted_text.trim_end();
76106

77-
let new_text = build_json_text(formatted_text, code_block.indent_text);
107+
let new_text = if code_block.is_array {
108+
build_array_json_text(formatted_text, code_block.indent_text)
109+
} else {
110+
serde_json::to_string(&formatted_text).unwrap()
111+
};
78112

79113
Some(TextChange {
80114
range: code_block.replace_range,
@@ -83,6 +117,9 @@ fn get_cell_text_change(
83117
}
84118

85119
struct CodeBlockText<'a> {
120+
// Can be either a string or an array of strings.
121+
// (https://github.com/jupyter/nbformat/blob/0708dd627d9ef81b12f231defb0d94dd7e80e3f4/nbformat/v4/nbformat.v4.5.schema.json#L460C7-L468C8)
122+
is_array: bool,
86123
indent_text: &'a str,
87124
replace_range: std::ops::Range<usize>,
88125
source: String,
@@ -91,8 +128,10 @@ struct CodeBlockText<'a> {
91128
fn analyze_code_block<'a>(cell: &jsonc_parser::ast::Object<'a>, file_text: &'a str) -> Option<CodeBlockText<'a>> {
92129
let mut indent_text = "";
93130
let mut replace_range = std::ops::Range::default();
131+
let mut is_array = false;
94132
let cell_source = match &cell.get("source")?.value {
95133
jsonc_parser::ast::Value::Array(items) => {
134+
is_array = true;
96135
let mut strings = Vec::with_capacity(items.elements.len());
97136
for (i, element) in items.elements.iter().enumerate() {
98137
let string_lit = element.as_string_lit()?;
@@ -112,18 +151,22 @@ fn analyze_code_block<'a>(cell: &jsonc_parser::ast::Object<'a>, file_text: &'a s
112151
}
113152
text
114153
}
115-
jsonc_parser::ast::Value::StringLit(string) => string.value.to_string(),
154+
jsonc_parser::ast::Value::StringLit(string) => {
155+
replace_range = string.range.start..string.range.end;
156+
string.value.to_string()
157+
}
116158
_ => return None,
117159
};
118160
Some(CodeBlockText {
161+
is_array,
119162
indent_text,
120163
replace_range,
121164
source: cell_source,
122165
})
123166
}
124167

125168
/// Turn the formatted text into a json array, split up by line breaks.
126-
fn build_json_text(formatted_text: &str, indent_text: &str) -> String {
169+
fn build_array_json_text(formatted_text: &str, indent_text: &str) -> String {
127170
let mut new_text = String::new();
128171
let mut current_end_index = 0;
129172
for (i, line) in formatted_text.split('\n').enumerate() {

tests/specs/Basic.txt

+48
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,51 @@
8383
"nbformat": 4,
8484
"nbformat_minor": 2
8585
}
86+
87+
== should format cell with string value ==
88+
{
89+
"cells": [
90+
{
91+
"cell_type": "code",
92+
"execution_count": null,
93+
"metadata": {
94+
"vscode": {
95+
"languageId": "typescript"
96+
}
97+
},
98+
"outputs": [],
99+
"source": "code block 1\ntesting"
100+
}
101+
],
102+
"metadata": {
103+
"language_info": {
104+
"name": "python"
105+
}
106+
},
107+
"nbformat": 4,
108+
"nbformat_minor": 2
109+
}
110+
111+
[expect]
112+
{
113+
"cells": [
114+
{
115+
"cell_type": "code",
116+
"execution_count": null,
117+
"metadata": {
118+
"vscode": {
119+
"languageId": "typescript"
120+
}
121+
},
122+
"outputs": [],
123+
"source": "code block 1\ntesting_typescript"
124+
}
125+
],
126+
"metadata": {
127+
"language_info": {
128+
"name": "python"
129+
}
130+
},
131+
"nbformat": 4,
132+
"nbformat_minor": 2
133+
}

0 commit comments

Comments
 (0)