1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

feat(fmt): support SQL (#26750)

This commit adds support for .sql files in "deno fmt" subcommand.

Closes: https://github.com/denoland/deno/issues/25024
---------

Signed-off-by: m4rc3l05 <15786310+M4RC3L05@users.noreply.github.com>
Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
João Baptista 2024-11-19 21:01:16 +00:00 committed by GitHub
parent 628816448e
commit c55e936be0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 247 additions and 5 deletions

12
Cargo.lock generated
View file

@ -1291,6 +1291,7 @@ dependencies = [
"sha2", "sha2",
"shell-escape", "shell-escape",
"spki", "spki",
"sqlformat",
"strsim", "strsim",
"tar", "tar",
"tempfile", "tempfile",
@ -6794,6 +6795,17 @@ dependencies = [
"der", "der",
] ]
[[package]]
name = "sqlformat"
version = "0.3.1"
source = "git+https://github.com/shssoichiro/sqlformat-rs.git?rev=827d639#827d639bef94d8e5a5a0e29b41185c8d572f24e6"
dependencies = [
"nom 7.1.3",
"once_cell",
"regex",
"unicode_categories",
]
[[package]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.0" version = "1.2.0"

View file

@ -151,6 +151,8 @@ serde_repr.workspace = true
sha2.workspace = true sha2.workspace = true
shell-escape = "=0.1.5" shell-escape = "=0.1.5"
spki = { version = "0.7", features = ["pem"] } spki = { version = "0.7", features = ["pem"] }
# NOTE(bartlomieju): for now using github URL, because 0.3.2 with important fixes hasn't been released yet.
sqlformat = { git = "https://github.com/shssoichiro/sqlformat-rs.git", rev = "827d639" }
strsim = "0.11.1" strsim = "0.11.1"
tar.workspace = true tar.workspace = true
tempfile.workspace = true tempfile.workspace = true

View file

@ -210,6 +210,7 @@ pub struct FmtFlags {
pub no_semicolons: Option<bool>, pub no_semicolons: Option<bool>,
pub watch: Option<WatchFlags>, pub watch: Option<WatchFlags>,
pub unstable_component: bool, pub unstable_component: bool,
pub unstable_sql: bool,
} }
impl FmtFlags { impl FmtFlags {
@ -2291,7 +2292,7 @@ Ignore formatting a file by adding an ignore comment at the top of the file:
.value_parser([ .value_parser([
"ts", "tsx", "js", "jsx", "md", "json", "jsonc", "css", "scss", "ts", "tsx", "js", "jsx", "md", "json", "jsonc", "css", "scss",
"sass", "less", "html", "svelte", "vue", "astro", "yml", "yaml", "sass", "less", "html", "svelte", "vue", "astro", "yml", "yaml",
"ipynb", "ipynb", "sql"
]) ])
.help_heading(FMT_HEADING).requires("files"), .help_heading(FMT_HEADING).requires("files"),
) )
@ -2410,6 +2411,14 @@ Ignore formatting a file by adding an ignore comment at the top of the file:
.help_heading(FMT_HEADING) .help_heading(FMT_HEADING)
.hide(true), .hide(true),
) )
.arg(
Arg::new("unstable-sql")
.long("unstable-sql")
.help("Enable formatting SQL files.")
.value_parser(FalseyValueParser::new())
.action(ArgAction::SetTrue)
.help_heading(FMT_HEADING),
)
}) })
} }
@ -4634,6 +4643,7 @@ fn fmt_parse(
let prose_wrap = matches.remove_one::<String>("prose-wrap"); let prose_wrap = matches.remove_one::<String>("prose-wrap");
let no_semicolons = matches.remove_one::<bool>("no-semicolons"); let no_semicolons = matches.remove_one::<bool>("no-semicolons");
let unstable_component = matches.get_flag("unstable-component"); let unstable_component = matches.get_flag("unstable-component");
let unstable_sql = matches.get_flag("unstable-sql");
flags.subcommand = DenoSubcommand::Fmt(FmtFlags { flags.subcommand = DenoSubcommand::Fmt(FmtFlags {
check: matches.get_flag("check"), check: matches.get_flag("check"),
@ -4646,6 +4656,7 @@ fn fmt_parse(
no_semicolons, no_semicolons,
watch: watch_arg_parse(matches)?, watch: watch_arg_parse(matches)?,
unstable_component, unstable_component,
unstable_sql,
}); });
Ok(()) Ok(())
} }
@ -6565,6 +6576,7 @@ mod tests {
prose_wrap: None, prose_wrap: None,
no_semicolons: None, no_semicolons: None,
unstable_component: false, unstable_component: false,
unstable_sql: false,
watch: Default::default(), watch: Default::default(),
}), }),
..Flags::default() ..Flags::default()
@ -6588,6 +6600,7 @@ mod tests {
prose_wrap: None, prose_wrap: None,
no_semicolons: None, no_semicolons: None,
unstable_component: false, unstable_component: false,
unstable_sql: false,
watch: Default::default(), watch: Default::default(),
}), }),
..Flags::default() ..Flags::default()
@ -6611,6 +6624,7 @@ mod tests {
prose_wrap: None, prose_wrap: None,
no_semicolons: None, no_semicolons: None,
unstable_component: false, unstable_component: false,
unstable_sql: false,
watch: Default::default(), watch: Default::default(),
}), }),
..Flags::default() ..Flags::default()
@ -6634,6 +6648,7 @@ mod tests {
prose_wrap: None, prose_wrap: None,
no_semicolons: None, no_semicolons: None,
unstable_component: false, unstable_component: false,
unstable_sql: false,
watch: Some(Default::default()), watch: Some(Default::default()),
}), }),
..Flags::default() ..Flags::default()
@ -6648,7 +6663,8 @@ mod tests {
"--unstable-css", "--unstable-css",
"--unstable-html", "--unstable-html",
"--unstable-component", "--unstable-component",
"--unstable-yaml" "--unstable-yaml",
"--unstable-sql"
]); ]);
assert_eq!( assert_eq!(
r.unwrap(), r.unwrap(),
@ -6666,6 +6682,7 @@ mod tests {
prose_wrap: None, prose_wrap: None,
no_semicolons: None, no_semicolons: None,
unstable_component: true, unstable_component: true,
unstable_sql: true,
watch: Some(WatchFlags { watch: Some(WatchFlags {
hmr: false, hmr: false,
no_clear_screen: true, no_clear_screen: true,
@ -6700,6 +6717,7 @@ mod tests {
prose_wrap: None, prose_wrap: None,
no_semicolons: None, no_semicolons: None,
unstable_component: false, unstable_component: false,
unstable_sql: false,
watch: Some(Default::default()), watch: Some(Default::default()),
}), }),
..Flags::default() ..Flags::default()
@ -6723,6 +6741,7 @@ mod tests {
prose_wrap: None, prose_wrap: None,
no_semicolons: None, no_semicolons: None,
unstable_component: false, unstable_component: false,
unstable_sql: false,
watch: Default::default(), watch: Default::default(),
}), }),
config_flag: ConfigFlag::Path("deno.jsonc".to_string()), config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
@ -6754,6 +6773,7 @@ mod tests {
prose_wrap: None, prose_wrap: None,
no_semicolons: None, no_semicolons: None,
unstable_component: false, unstable_component: false,
unstable_sql: false,
watch: Some(Default::default()), watch: Some(Default::default()),
}), }),
config_flag: ConfigFlag::Path("deno.jsonc".to_string()), config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
@ -6790,6 +6810,7 @@ mod tests {
prose_wrap: Some("never".to_string()), prose_wrap: Some("never".to_string()),
no_semicolons: Some(true), no_semicolons: Some(true),
unstable_component: false, unstable_component: false,
unstable_sql: false,
watch: Default::default(), watch: Default::default(),
}), }),
..Flags::default() ..Flags::default()
@ -6820,6 +6841,7 @@ mod tests {
prose_wrap: None, prose_wrap: None,
no_semicolons: Some(false), no_semicolons: Some(false),
unstable_component: false, unstable_component: false,
unstable_sql: false,
watch: Default::default(), watch: Default::default(),
}), }),
..Flags::default() ..Flags::default()
@ -6845,6 +6867,7 @@ mod tests {
prose_wrap: None, prose_wrap: None,
no_semicolons: None, no_semicolons: None,
unstable_component: false, unstable_component: false,
unstable_sql: false,
watch: Default::default(), watch: Default::default(),
}), }),
ext: Some("html".to_string()), ext: Some("html".to_string()),

View file

@ -289,6 +289,7 @@ impl BenchOptions {
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct UnstableFmtOptions { pub struct UnstableFmtOptions {
pub component: bool, pub component: bool,
pub sql: bool,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -322,6 +323,7 @@ impl FmtOptions {
options: resolve_fmt_options(fmt_flags, fmt_config.options), options: resolve_fmt_options(fmt_flags, fmt_config.options),
unstable: UnstableFmtOptions { unstable: UnstableFmtOptions {
component: unstable.component || fmt_flags.unstable_component, component: unstable.component || fmt_flags.unstable_component,
sql: unstable.sql || fmt_flags.unstable_sql,
}, },
files: fmt_config.files, files: fmt_config.files,
} }
@ -1319,6 +1321,7 @@ impl CliOptions {
let workspace = self.workspace(); let workspace = self.workspace();
UnstableFmtOptions { UnstableFmtOptions {
component: workspace.has_unstable("fmt-component"), component: workspace.has_unstable("fmt-component"),
sql: workspace.has_unstable("fmt-sql"),
} }
} }
@ -1667,6 +1670,7 @@ impl CliOptions {
"byonm", "byonm",
"bare-node-builtins", "bare-node-builtins",
"fmt-component", "fmt-component",
"fmt-sql",
]) ])
.collect(); .collect();

View file

@ -1405,6 +1405,9 @@ impl Inner {
component: config_data component: config_data
.map(|d| d.unstable.contains("fmt-component")) .map(|d| d.unstable.contains("fmt-component"))
.unwrap_or(false), .unwrap_or(false),
sql: config_data
.map(|d| d.unstable.contains("fmt-sql"))
.unwrap_or(false),
}; };
let document = document.clone(); let document = document.clone();
move || { move || {

View file

@ -557,6 +557,7 @@
"ffi", "ffi",
"fs", "fs",
"fmt-component", "fmt-component",
"fmt-sql",
"http", "http",
"kv", "kv",
"net", "net",

View file

@ -272,6 +272,7 @@ fn format_markdown(
| "njk" | "njk"
| "yml" | "yml"
| "yaml" | "yaml"
| "sql"
) { ) {
// It's important to tell dprint proper file extension, otherwise // It's important to tell dprint proper file extension, otherwise
// it might parse the file twice. // it might parse the file twice.
@ -301,6 +302,13 @@ fn format_markdown(
} }
} }
"yml" | "yaml" => format_yaml(text, fmt_options), "yml" | "yaml" => format_yaml(text, fmt_options),
"sql" => {
if unstable_options.sql {
format_sql(text, fmt_options)
} else {
Ok(None)
}
}
_ => { _ => {
let mut codeblock_config = let mut codeblock_config =
get_resolved_typescript_config(fmt_options); get_resolved_typescript_config(fmt_options);
@ -503,7 +511,48 @@ pub fn format_html(
}) })
} }
/// Formats a single TS, TSX, JS, JSX, JSONC, JSON, MD, or IPYNB file. pub fn format_sql(
file_text: &str,
fmt_options: &FmtOptionsConfig,
) -> Result<Option<String>, AnyError> {
let ignore_file = file_text
.lines()
.take_while(|line| line.starts_with("--"))
.any(|line| {
line
.strip_prefix("--")
.unwrap()
.trim()
.starts_with("deno-fmt-ignore-file")
});
if ignore_file {
return Ok(None);
}
let mut formatted_str = sqlformat::format(
file_text,
&sqlformat::QueryParams::None,
&sqlformat::FormatOptions {
ignore_case_convert: None,
indent: if fmt_options.use_tabs.unwrap_or_default() {
sqlformat::Indent::Tabs
} else {
sqlformat::Indent::Spaces(fmt_options.indent_width.unwrap_or(2))
},
// leave one blank line between queries.
lines_between_queries: 2,
uppercase: Some(true),
},
);
// Add single new line to the end of file.
formatted_str.push('\n');
Ok(Some(formatted_str))
}
/// Formats a single TS, TSX, JS, JSX, JSONC, JSON, MD, IPYNB or SQL file.
pub fn format_file( pub fn format_file(
file_path: &Path, file_path: &Path,
file_text: &str, file_text: &str,
@ -538,6 +587,13 @@ pub fn format_file(
format_file(file_path, &file_text, fmt_options, unstable_options, None) format_file(file_path, &file_text, fmt_options, unstable_options, None)
}, },
), ),
"sql" => {
if unstable_options.sql {
format_sql(file_text, fmt_options)
} else {
Ok(None)
}
}
_ => { _ => {
let config = get_resolved_typescript_config(fmt_options); let config = get_resolved_typescript_config(fmt_options);
dprint_plugin_typescript::format_text( dprint_plugin_typescript::format_text(
@ -1209,6 +1265,7 @@ fn is_supported_ext_fmt(path: &Path) -> bool {
| "yml" | "yml"
| "yaml" | "yaml"
| "ipynb" | "ipynb"
| "sql"
) )
}) })
} }
@ -1269,6 +1326,11 @@ mod test {
assert!(is_supported_ext_fmt(Path::new("foo.yaml"))); assert!(is_supported_ext_fmt(Path::new("foo.yaml")));
assert!(is_supported_ext_fmt(Path::new("foo.YaML"))); assert!(is_supported_ext_fmt(Path::new("foo.YaML")));
assert!(is_supported_ext_fmt(Path::new("foo.ipynb"))); assert!(is_supported_ext_fmt(Path::new("foo.ipynb")));
assert!(is_supported_ext_fmt(Path::new("foo.sql")));
assert!(is_supported_ext_fmt(Path::new("foo.Sql")));
assert!(is_supported_ext_fmt(Path::new("foo.sQl")));
assert!(is_supported_ext_fmt(Path::new("foo.sqL")));
assert!(is_supported_ext_fmt(Path::new("foo.SQL")));
} }
#[test] #[test]

View file

@ -61,6 +61,12 @@ fn fmt_test() {
let badly_formatted_yaml = t.path().join("badly_formatted.yaml"); let badly_formatted_yaml = t.path().join("badly_formatted.yaml");
badly_formatted_original_yaml.copy(&badly_formatted_yaml); badly_formatted_original_yaml.copy(&badly_formatted_yaml);
let fixed_sql = testdata_fmt_dir.join("badly_formatted_fixed.sql");
let badly_formatted_original_sql =
testdata_fmt_dir.join("badly_formatted.sql");
let badly_formatted_sql = t.path().join("badly_formatted.sql");
badly_formatted_original_sql.copy(&badly_formatted_sql);
// First, check formatting by ignoring the badly formatted file. // First, check formatting by ignoring the badly formatted file.
let output = context let output = context
.new_command() .new_command()
@ -71,11 +77,12 @@ fn fmt_test() {
"--unstable-html".to_string(), "--unstable-html".to_string(),
"--unstable-component".to_string(), "--unstable-component".to_string(),
"--unstable-yaml".to_string(), "--unstable-yaml".to_string(),
"--unstable-sql".to_string(),
format!( format!(
"--ignore={badly_formatted_js},{badly_formatted_md},{badly_formatted_json},{badly_formatted_css},{badly_formatted_html},{badly_formatted_component},{badly_formatted_yaml},{badly_formatted_ipynb}", "--ignore={badly_formatted_js},{badly_formatted_md},{badly_formatted_json},{badly_formatted_css},{badly_formatted_html},{badly_formatted_component},{badly_formatted_yaml},{badly_formatted_ipynb},{badly_formatted_sql}",
), ),
format!( format!(
"--check {badly_formatted_js} {badly_formatted_md} {badly_formatted_json} {badly_formatted_css} {badly_formatted_html} {badly_formatted_component} {badly_formatted_yaml} {badly_formatted_ipynb}", "--check {badly_formatted_js} {badly_formatted_md} {badly_formatted_json} {badly_formatted_css} {badly_formatted_html} {badly_formatted_component} {badly_formatted_yaml} {badly_formatted_ipynb} {badly_formatted_sql}",
), ),
]) ])
.run(); .run();
@ -95,6 +102,7 @@ fn fmt_test() {
"--unstable-html".to_string(), "--unstable-html".to_string(),
"--unstable-component".to_string(), "--unstable-component".to_string(),
"--unstable-yaml".to_string(), "--unstable-yaml".to_string(),
"--unstable-sql".to_string(),
badly_formatted_js.to_string(), badly_formatted_js.to_string(),
badly_formatted_md.to_string(), badly_formatted_md.to_string(),
badly_formatted_json.to_string(), badly_formatted_json.to_string(),
@ -103,6 +111,7 @@ fn fmt_test() {
badly_formatted_component.to_string(), badly_formatted_component.to_string(),
badly_formatted_yaml.to_string(), badly_formatted_yaml.to_string(),
badly_formatted_ipynb.to_string(), badly_formatted_ipynb.to_string(),
badly_formatted_sql.to_string(),
]) ])
.run(); .run();
@ -119,6 +128,7 @@ fn fmt_test() {
"--unstable-html".to_string(), "--unstable-html".to_string(),
"--unstable-component".to_string(), "--unstable-component".to_string(),
"--unstable-yaml".to_string(), "--unstable-yaml".to_string(),
"--unstable-sql".to_string(),
badly_formatted_js.to_string(), badly_formatted_js.to_string(),
badly_formatted_md.to_string(), badly_formatted_md.to_string(),
badly_formatted_json.to_string(), badly_formatted_json.to_string(),
@ -127,6 +137,7 @@ fn fmt_test() {
badly_formatted_component.to_string(), badly_formatted_component.to_string(),
badly_formatted_yaml.to_string(), badly_formatted_yaml.to_string(),
badly_formatted_ipynb.to_string(), badly_formatted_ipynb.to_string(),
badly_formatted_sql.to_string(),
]) ])
.run(); .run();
@ -141,6 +152,7 @@ fn fmt_test() {
let expected_component = fixed_component.read_to_string(); let expected_component = fixed_component.read_to_string();
let expected_yaml = fixed_yaml.read_to_string(); let expected_yaml = fixed_yaml.read_to_string();
let expected_ipynb = fixed_ipynb.read_to_string(); let expected_ipynb = fixed_ipynb.read_to_string();
let expected_sql = fixed_sql.read_to_string();
let actual_js = badly_formatted_js.read_to_string(); let actual_js = badly_formatted_js.read_to_string();
let actual_md = badly_formatted_md.read_to_string(); let actual_md = badly_formatted_md.read_to_string();
let actual_json = badly_formatted_json.read_to_string(); let actual_json = badly_formatted_json.read_to_string();
@ -149,6 +161,7 @@ fn fmt_test() {
let actual_component = badly_formatted_component.read_to_string(); let actual_component = badly_formatted_component.read_to_string();
let actual_yaml = badly_formatted_yaml.read_to_string(); let actual_yaml = badly_formatted_yaml.read_to_string();
let actual_ipynb = badly_formatted_ipynb.read_to_string(); let actual_ipynb = badly_formatted_ipynb.read_to_string();
let actual_sql = badly_formatted_sql.read_to_string();
assert_eq!(expected_js, actual_js); assert_eq!(expected_js, actual_js);
assert_eq!(expected_md, actual_md); assert_eq!(expected_md, actual_md);
assert_eq!(expected_json, actual_json); assert_eq!(expected_json, actual_json);
@ -157,6 +170,7 @@ fn fmt_test() {
assert_eq!(expected_component, actual_component); assert_eq!(expected_component, actual_component);
assert_eq!(expected_yaml, actual_yaml); assert_eq!(expected_yaml, actual_yaml);
assert_eq!(expected_ipynb, actual_ipynb); assert_eq!(expected_ipynb, actual_ipynb);
assert_eq!(expected_sql, actual_sql);
} }
#[test] #[test]

View file

@ -0,0 +1,25 @@
{
"tempDir": true,
"tests": {
"nothing": {
"args": "fmt",
"output": "Checked 7 files\n"
},
"flag": {
"args": "fmt --unstable-sql",
"output": "[UNORDERED_START]\n[WILDLINE]badly_formatted.sql\n[WILDLINE]well_formatted.sql\n[WILDLINE]wrong_file_ignore.sql\n[UNORDERED_END]\nChecked 7 files\n"
},
"config_file": {
"steps": [{
"args": [
"eval",
"Deno.writeTextFile('deno.json', '{\\n \"unstable\": [\"fmt-sql\"]\\n}\\n')"
],
"output": "[WILDCARD]"
}, {
"args": "fmt",
"output": "[UNORDERED_START]\n[WILDLINE]badly_formatted.sql\n[WILDLINE]well_formatted.sql\n[WILDLINE]wrong_file_ignore.sql\n[UNORDERED_END]\nChecked 8 files\n"
}]
}
}
}

View file

@ -0,0 +1 @@
select *;

View file

@ -0,0 +1,3 @@
-- deno-fmt-ignore-file
foo%!

View file

@ -0,0 +1,3 @@
--deno-fmt-ignore-file
foo%!

View file

@ -0,0 +1,6 @@
-- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
-- incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
-- quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
-- deno-fmt-ignore-file
foo%!

View file

@ -0,0 +1,3 @@
-- deno-fmt-ignore-file Foo bar biz
foo%!

View file

@ -0,0 +1,4 @@
SELECT
*
FROM
foo;

View file

@ -0,0 +1,6 @@
-- File ignore directive only works if it's in the first cluster
-- of comment, ie. there are no empty lines after the first n-leading lines.
-- deno-fmt-ignore-file
foo

View file

@ -63,3 +63,15 @@ function foo(): number {
let a:number let a:number
</script> </script>
``` ```
```sql
seLect * , biz, buz
from baz;
```
```sql
-- deno-fmt-ignore-file
seLect * , biz, buz
from baz;
```

21
tests/testdata/fmt/badly_formatted.sql vendored Normal file
View file

@ -0,0 +1,21 @@
select * from foo;
update foo set a = 'b'Where id = 'biz';
create table foo(id text not null
bar text,
biz int,
buz number NOT NULL
);
INSERT
into
user_data
(first_name,
last_name, address, phone, email)
VALUES
('foo', 'bar',
'biz', 1, 'bix');

View file

@ -56,3 +56,18 @@ function foo(): number {
let a: number; let a: number;
</script> </script>
``` ```
```sql
SELECT
*,
biz,
buz
FROM
baz;
```
```sql
-- deno-fmt-ignore-file
seLect * , biz, buz
from baz;
```

View file

@ -0,0 +1,22 @@
SELECT
*
FROM
foo;
UPDATE
foo
SET
a = 'b'
WHERE
id = 'biz';
CREATE TABLE foo(
id text NOT NULL bar text,
biz int,
buz number NOT NULL
);
INSERT INTO
user_data (first_name, last_name, address, phone, email)
VALUES
('foo', 'bar', 'biz', 1, 'bix');