feat(fmt): support formatting code blocks in Jupyter notebooks (#21310)

This commit is contained in:
scarf 2023-11-28 00:32:12 +09:00 committed by GitHub
parent 3d47c7eb1f
commit 2b7e145e56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 272 additions and 26 deletions

View File

@ -30,6 +30,7 @@
"cli/tests/testdata/file_extensions/ts_with_js_extension.js",
"cli/tests/testdata/fmt/badly_formatted.json",
"cli/tests/testdata/fmt/badly_formatted.md",
"cli/tests/testdata/fmt/badly_formatted.ipynb",
"cli/tests/testdata/byte_order_mark.ts",
"cli/tests/testdata/encoding",
"cli/tests/testdata/fmt/",

14
Cargo.lock generated
View File

@ -884,6 +884,7 @@ dependencies = [
"dissimilar",
"dotenvy",
"dprint-plugin-json",
"dprint-plugin-jupyter",
"dprint-plugin-markdown",
"dprint-plugin-typescript",
"encoding_rs",
@ -1940,6 +1941,19 @@ dependencies = [
"text_lines",
]
[[package]]
name = "dprint-plugin-jupyter"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b65cf5dd43b15bf9dbb69a2d0e331106d6e3d33d8678d32385245993e8855c02"
dependencies = [
"anyhow",
"dprint-core",
"jsonc-parser",
"serde",
"serde_json",
]
[[package]]
name = "dprint-plugin-markdown"
version = "0.16.3"

View File

@ -84,6 +84,7 @@ dotenvy = "0.15.7"
dprint-plugin-json = "=0.19.1"
dprint-plugin-markdown = "=0.16.3"
dprint-plugin-typescript = "=0.88.5"
dprint-plugin-jupyter = "=0.1.1"
encoding_rs.workspace = true
env_logger = "=0.10.0"
fancy-regex = "=0.10.0"

View File

@ -1579,7 +1579,9 @@ Ignore formatting a file by adding an ignore comment at the top of the file:
.help("Set content type of the supplied file")
// prefer using ts for formatting instead of js because ts works in more scenarios
.default_value("ts")
.value_parser(["ts", "tsx", "js", "jsx", "md", "json", "jsonc"]),
.value_parser([
"ts", "tsx", "js", "jsx", "md", "json", "jsonc", "ipynb",
]),
)
.arg(
Arg::new("ignore")

View File

@ -359,11 +359,7 @@ fn create_cli_snapshot(snapshot_path: PathBuf) -> CreateSnapshotOutput {
// Ideally we could deduplicate that code.
fn deno_version() -> String {
if env::var("DENO_CANARY").is_ok() {
format!(
"{}+{}",
env!("CARGO_PKG_VERSION"),
git_commit_hash()[..7].to_string()
)
format!("{}+{}", env!("CARGO_PKG_VERSION"), &git_commit_hash()[..7])
} else {
env!("CARGO_PKG_VERSION").to_string()
}

View File

@ -27,18 +27,24 @@ fn fmt_test() {
testdata_fmt_dir.join("badly_formatted.json");
let badly_formatted_json = t.path().join("badly_formatted.json");
badly_formatted_original_json.copy(&badly_formatted_json);
// First, check formatting by ignoring the badly formatted file.
let fixed_ipynb = testdata_fmt_dir.join("badly_formatted_fixed.ipynb");
let badly_formatted_original_ipynb =
testdata_fmt_dir.join("badly_formatted.ipynb");
let badly_formatted_ipynb = t.path().join("badly_formatted.ipynb");
badly_formatted_original_ipynb.copy(&badly_formatted_ipynb);
// First, check formatting by ignoring the badly formatted file.
let output = context
.new_command()
.current_dir(&testdata_fmt_dir)
.args_vec(vec![
"fmt".to_string(),
format!(
"--ignore={badly_formatted_js},{badly_formatted_md},{badly_formatted_json}",
"--ignore={badly_formatted_js},{badly_formatted_md},{badly_formatted_json},{badly_formatted_ipynb}",
),
format!(
"--check {badly_formatted_js} {badly_formatted_md} {badly_formatted_json}",
"--check {badly_formatted_js} {badly_formatted_md} {badly_formatted_json} {badly_formatted_ipynb}",
),
])
.run();
@ -57,6 +63,7 @@ fn fmt_test() {
badly_formatted_js.to_string(),
badly_formatted_md.to_string(),
badly_formatted_json.to_string(),
badly_formatted_ipynb.to_string(),
])
.run();
@ -72,6 +79,7 @@ fn fmt_test() {
badly_formatted_js.to_string(),
badly_formatted_md.to_string(),
badly_formatted_json.to_string(),
badly_formatted_ipynb.to_string(),
])
.run();
@ -81,12 +89,15 @@ fn fmt_test() {
let expected_js = fixed_js.read_to_string();
let expected_md = fixed_md.read_to_string();
let expected_json = fixed_json.read_to_string();
let expected_ipynb = fixed_ipynb.read_to_string();
let actual_js = badly_formatted_js.read_to_string();
let actual_md = badly_formatted_md.read_to_string();
let actual_json = badly_formatted_json.read_to_string();
let actual_ipynb = badly_formatted_ipynb.read_to_string();
assert_eq!(expected_js, actual_js);
assert_eq!(expected_md, actual_md);
assert_eq!(expected_json, actual_json);
assert_eq!(expected_ipynb, actual_ipynb);
}
#[test]
@ -198,6 +209,12 @@ itest!(fmt_stdin_json {
output_str: Some("{ \"key\": \"value\" }\n"),
});
itest!(fmt_stdin_ipynb {
args: "fmt --ext=ipynb -",
input: Some(include_str!("../testdata/fmt/badly_formatted.ipynb")),
output_str: Some(include_str!("../testdata/fmt/badly_formatted_fixed.ipynb")),
});
itest!(fmt_stdin_check_formatted {
args: "fmt --check -",
input: Some("const a = 1;\n"),

View File

@ -0,0 +1,105 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Hello Markdown\n",
"this isn't formatted properly"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hello World\n"
]
}
],
"source": [
"console.log(\"Hello World\"\n",
"\n",
");"
]
},
{
"cell_type": "raw",
"metadata": {},
"source": [
"raw text\n",
" here too\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"alice\n"
]
}
],
"source": [
"function hello(name: string ) {\n",
" console.log(name);\n",
"};\n",
"\n",
"hello( \"alice\");\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"function foo(): number {\n",
" return 2;\n",
"}\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong\n"
]
}
],
"source": [
"console.log(\"loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong\");"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Deno",
"language": "typescript",
"name": "deno"
},
"language_info": {
"file_extension": ".ts",
"mimetype": "text/x.typescript",
"name": "typescript",
"nb_converter": "script",
"pygments_lexer": "typescript",
"version": "5.2.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -0,0 +1,106 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Hello Markdown\n",
"\n",
"this isn't formatted properly"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hello World\n"
]
}
],
"source": [
"console.log(\"Hello World\");"
]
},
{
"cell_type": "raw",
"metadata": {},
"source": [
"raw text\n",
" here too\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"alice\n"
]
}
],
"source": [
"function hello(name: string) {\n",
" console.log(name);\n",
"}\n",
"\n",
"hello(\"alice\");"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"function foo(): number {\n",
" return 2;\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong\n"
]
}
],
"source": [
"console.log(\n",
" \"loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong\",\n",
");"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Deno",
"language": "typescript",
"name": "deno"
},
"language_info": {
"file_extension": ".ts",
"mimetype": "text/x.typescript",
"name": "typescript",
"nb_converter": "script",
"pygments_lexer": "typescript",
"version": "5.2.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -1,2 +0,0 @@
[WILDCARD]
error: Found 1 not formatted file in [WILDCARD] files

View File

@ -223,23 +223,29 @@ pub fn format_json(
dprint_plugin_json::format_text(file_path, file_text, &config)
}
/// Formats a single TS, TSX, JS, JSX, JSONC, JSON, or MD file.
/// Formats a single TS, TSX, JS, JSX, JSONC, JSON, MD, or IPYNB file.
pub fn format_file(
file_path: &Path,
file_text: &str,
fmt_options: &FmtOptionsConfig,
) -> Result<Option<String>, AnyError> {
let ext = get_extension(file_path).unwrap_or_default();
if matches!(
ext.as_str(),
"md" | "mkd" | "mkdn" | "mdwn" | "mdown" | "markdown"
) {
format_markdown(file_text, fmt_options)
} else if matches!(ext.as_str(), "json" | "jsonc") {
format_json(file_path, file_text, fmt_options)
} else {
let config = get_resolved_typescript_config(fmt_options);
dprint_plugin_typescript::format_text(file_path, file_text, &config)
match ext.as_str() {
"md" | "mkd" | "mkdn" | "mdwn" | "mdown" | "markdown" => {
format_markdown(file_text, fmt_options)
}
"json" | "jsonc" => format_json(file_path, file_text, fmt_options),
"ipynb" => dprint_plugin_jupyter::format_text(
file_text,
|file_path: &Path, file_text: String| {
format_file(file_path, &file_text, fmt_options)
},
),
_ => {
let config = get_resolved_typescript_config(fmt_options);
dprint_plugin_typescript::format_text(file_path, file_text, &config)
}
}
}
@ -667,7 +673,7 @@ where
/// This function is similar to is_supported_ext but adds additional extensions
/// supported by `deno fmt`.
fn is_supported_ext_fmt(path: &Path) -> bool {
if let Some(ext) = get_extension(path) {
get_extension(path).is_some_and(|ext| {
matches!(
ext.as_str(),
"ts"
@ -686,10 +692,9 @@ fn is_supported_ext_fmt(path: &Path) -> bool {
| "mdwn"
| "mdown"
| "markdown"
| "ipynb"
)
} else {
false
}
})
}
#[cfg(test)]
@ -721,6 +726,7 @@ mod test {
assert!(is_supported_ext_fmt(Path::new("foo.JSONC")));
assert!(is_supported_ext_fmt(Path::new("foo.json")));
assert!(is_supported_ext_fmt(Path::new("foo.JsON")));
assert!(is_supported_ext_fmt(Path::new("foo.ipynb")));
}
#[test]