From f4ccb816b74ffabaad82b0d4b8e717719609004a Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 17 Sep 2024 16:11:48 +0100 Subject: [PATCH] feat: ability to sort type only named imports and exports first or last --- deployment/schema.json | 21 +++++ src/configuration/builder.rs | 18 +++- src/configuration/resolve_config.rs | 12 +++ src/configuration/types.rs | 15 ++++ src/generation/generate.rs | 14 ++- src/generation/sorting/mod.rs | 90 ++++++++++++++----- ...amedExports_SortNamedTypeExports_First.txt | 18 ++++ ...NamedExports_SortNamedTypeExports_Last.txt | 18 ++++ ...NamedExports_SortNamedTypeExports_None.txt | 18 ++++ ...NamedExports_SortOrder_CaseInsensitive.txt | 18 ++++ ...amedImports_SortNamedTypeImports_First.txt | 18 ++++ ...NamedImports_SortNamedTypeImports_Last.txt | 18 ++++ ...NamedImports_SortNamedTypeImports_None.txt | 18 ++++ ...NamedImports_SortOrder_CaseInsensitive.txt | 18 ++++ 14 files changed, 286 insertions(+), 28 deletions(-) create mode 100644 tests/specs/declarations/export/NamedExports_SortNamedTypeExports_First.txt create mode 100644 tests/specs/declarations/export/NamedExports_SortNamedTypeExports_Last.txt create mode 100644 tests/specs/declarations/export/NamedExports_SortNamedTypeExports_None.txt create mode 100644 tests/specs/declarations/import/NamedImports_SortNamedTypeImports_First.txt create mode 100644 tests/specs/declarations/import/NamedImports_SortNamedTypeImports_Last.txt create mode 100644 tests/specs/declarations/import/NamedImports_SortNamedTypeImports_None.txt diff --git a/deployment/schema.json b/deployment/schema.json index 688bea32..85b6688b 100644 --- a/deployment/schema.json +++ b/deployment/schema.json @@ -297,6 +297,21 @@ "description": "Force multiple lines only if importing more than one thing." }] }, + "typeOnlyImportsExportsSortOrder": { + "description": "The kind of sort ordering to use for typed imports and exports.", + "type": "string", + "default": "last", + "oneOf": [{ + "const": "first", + "description": "Puts type-only named imports and exports first." + }, { + "const": "last", + "description": "Puts type-only named imports and exports last." + }, { + "const": "none", + "description": "Does not sort based on if a type-only named import or export." + }] + }, "sortOrder": { "description": "The kind of sort ordering to use.", "type": "string", @@ -1000,9 +1015,15 @@ "exportDeclaration.sortNamedExports": { "$ref": "#/definitions/sortOrder" }, + "exportDeclaration.sortTypeOnlyExports": { + "$ref": "#/definitions/typeOnlyImportsExportsSortOrder" + }, "importDeclaration.sortNamedImports": { "$ref": "#/definitions/sortOrder" }, + "importDeclaration.sortTypeOnlyImports": { + "$ref": "#/definitions/typeOnlyImportsExportsSortOrder" + }, "ignoreNodeCommentText": { "description": "The text to use for an ignore comment (ex. `// dprint-ignore`).", "default": "dprint-ignore", diff --git a/src/configuration/builder.rs b/src/configuration/builder.rs index 2145e74a..adac15f5 100644 --- a/src/configuration/builder.rs +++ b/src/configuration/builder.rs @@ -536,6 +536,13 @@ impl ConfigurationBuilder { self.insert("importDeclaration.sortNamedImports", value.to_string().into()) } + /// Sorts type-only named imports first, last, or none (no sorting). + /// + /// Default: Last + pub fn import_declaration_sort_type_only_imports(&mut self, value: NamedTypeImportsExportsOrder) -> &mut Self { + self.insert("importDeclaration.sortTypeOnlyImports", value.to_string().into()) + } + /// Alphabetically sorts the export declaration's named exports. /// /// Default: Case insensitive @@ -543,6 +550,13 @@ impl ConfigurationBuilder { self.insert("exportDeclaration.sortNamedExports", value.to_string().into()) } + /// Sorts type-only named exports first, last, or none (no sorting). + /// + /// Default: Last + pub fn export_declaration_sort_type_only_exports(&mut self, value: NamedTypeImportsExportsOrder) -> &mut Self { + self.insert("exportDeclaration.sortTypeOnlyExports", value.to_string().into()) + } + /* ignore comments */ /// The text to use for an ignore comment (ex. `// dprint-ignore`). @@ -1105,6 +1119,8 @@ mod tests { .module_sort_export_declarations(SortOrder::Maintain) .import_declaration_sort_named_imports(SortOrder::Maintain) .export_declaration_sort_named_exports(SortOrder::Maintain) + .import_declaration_sort_type_only_imports(NamedTypeImportsExportsOrder::First) + .export_declaration_sort_type_only_exports(NamedTypeImportsExportsOrder::None) /* ignore comments */ .ignore_node_comment_text("ignore") .ignore_file_comment_text("ignore-file") @@ -1269,7 +1285,7 @@ mod tests { .while_statement_space_around(true); let inner_config = config.get_inner_config(); - assert_eq!(inner_config.len(), 179); + assert_eq!(inner_config.len(), 181); let diagnostics = resolve_config(inner_config, &Default::default()).diagnostics; assert_eq!(diagnostics.len(), 0); } diff --git a/src/configuration/resolve_config.rs b/src/configuration/resolve_config.rs index cf51a365..b299cc29 100644 --- a/src/configuration/resolve_config.rs +++ b/src/configuration/resolve_config.rs @@ -119,6 +119,18 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration) module_sort_export_declarations: get_value(&mut config, "module.sortExportDeclarations", SortOrder::CaseInsensitive, &mut diagnostics), import_declaration_sort_named_imports: get_value(&mut config, "importDeclaration.sortNamedImports", SortOrder::CaseInsensitive, &mut diagnostics), export_declaration_sort_named_exports: get_value(&mut config, "exportDeclaration.sortNamedExports", SortOrder::CaseInsensitive, &mut diagnostics), + import_declaration_sort_type_only_imports: get_value( + &mut config, + "importDeclaration.sortTypeOnlyImports", + NamedTypeImportsExportsOrder::Last, + &mut diagnostics, + ), + export_declaration_sort_type_only_exports: get_value( + &mut config, + "exportDeclaration.sortTypeOnlyExports", + NamedTypeImportsExportsOrder::Last, + &mut diagnostics, + ), /* ignore comments */ ignore_node_comment_text: get_value(&mut config, "ignoreNodeCommentText", String::from("dprint-ignore"), &mut diagnostics), ignore_file_comment_text: get_value(&mut config, "ignoreFileCommentText", String::from("dprint-ignore-file"), &mut diagnostics), diff --git a/src/configuration/types.rs b/src/configuration/types.rs index 1011733d..b5485767 100644 --- a/src/configuration/types.rs +++ b/src/configuration/types.rs @@ -296,6 +296,17 @@ generate_str_to_from![ [CaseInsensitive, "caseInsensitive"] ]; +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub enum NamedTypeImportsExportsOrder { + First, + #[default] + Last, + None, +} + +generate_str_to_from![NamedTypeImportsExportsOrder, [First, "first"], [Last, "last"], [None, "none"]]; + #[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Configuration { @@ -336,8 +347,12 @@ pub struct Configuration { pub module_sort_export_declarations: SortOrder, #[serde(rename = "importDeclaration.sortNamedImports")] pub import_declaration_sort_named_imports: SortOrder, + #[serde(rename = "importDeclaration.sortTypeOnlyImports")] + pub import_declaration_sort_type_only_imports: NamedTypeImportsExportsOrder, #[serde(rename = "exportDeclaration.sortNamedExports")] pub export_declaration_sort_named_exports: SortOrder, + #[serde(rename = "exportDeclaration.sortTypeOnlyExports")] + pub export_declaration_sort_type_only_exports: NamedTypeImportsExportsOrder, /* ignore comments */ pub ignore_node_comment_text: String, pub ignore_file_comment_text: String, diff --git a/src/generation/generate.rs b/src/generation/generate.rs index 6d344d7b..236c22e6 100644 --- a/src/generation/generate.rs +++ b/src/generation/generate.rs @@ -1544,8 +1544,14 @@ fn gen_named_import_or_export_specifiers<'a>(opts: GenNamedImportOrExportSpecifi context: &Context<'a>, ) -> Option>), (usize, Option>), Program<'a>) -> std::cmp::Ordering>> { match parent_decl { - Node::NamedExport(_) => get_node_sorter_from_order(context.config.export_declaration_sort_named_exports), - Node::ImportDecl(_) => get_node_sorter_from_order(context.config.import_declaration_sort_named_imports), + Node::NamedExport(_) => get_node_sorter_from_order( + context.config.export_declaration_sort_named_exports, + context.config.export_declaration_sort_type_only_exports, + ), + Node::ImportDecl(_) => get_node_sorter_from_order( + context.config.import_declaration_sort_named_imports, + context.config.import_declaration_sort_type_only_imports, + ), _ => unreachable!(), } } @@ -7104,8 +7110,8 @@ fn gen_statements<'a>(inner_range: SourceRange, stmts: Vec>, context: & context: &Context<'a>, ) -> Option>), (usize, Option>), Program<'a>) -> std::cmp::Ordering>> { match group_kind { - StmtGroupKind::Imports => get_node_sorter_from_order(context.config.module_sort_import_declarations), - StmtGroupKind::Exports => get_node_sorter_from_order(context.config.module_sort_export_declarations), + StmtGroupKind::Imports => get_node_sorter_from_order(context.config.module_sort_import_declarations, NamedTypeImportsExportsOrder::None), + StmtGroupKind::Exports => get_node_sorter_from_order(context.config.module_sort_export_declarations, NamedTypeImportsExportsOrder::None), StmtGroupKind::Other => None, } } diff --git a/src/generation/sorting/mod.rs b/src/generation/sorting/mod.rs index 83d401d9..59a5b79a 100644 --- a/src/generation/sorting/mod.rs +++ b/src/generation/sorting/mod.rs @@ -8,19 +8,23 @@ use std::cmp::Ordering; use crate::configuration::*; -// a little rough, but good enough for our purposes +// very rough... this should be improved to not allocate so much +// and be cleaner -pub fn get_node_sorter_from_order<'a>(order: SortOrder) -> Option>), (usize, Option>), Program<'a>) -> Ordering>> { +pub fn get_node_sorter_from_order<'a>( + order: SortOrder, + named_type_imports_exports_order: NamedTypeImportsExportsOrder, +) -> Option>), (usize, Option>), Program<'a>) -> Ordering>> { // todo: how to reduce code duplication here? match order { SortOrder::Maintain => None, - SortOrder::CaseInsensitive => Some(Box::new(|(a_index, a), (b_index, b), program| { + SortOrder::CaseInsensitive => Some(Box::new(move |(a_index, a), (b_index, b), program| { let result = if is_import_or_export_declaration(&a) { - cmp_optional_nodes(a, b, program, |a, b, module| { + cmp_optional_nodes(a, b, program, named_type_imports_exports_order, |a, b, module| { cmp_module_specifiers(a.text_fast(module), b.text_fast(module), cmp_text_case_insensitive) }) } else { - cmp_optional_nodes(a, b, program, |a, b, module| { + cmp_optional_nodes(a, b, program, named_type_imports_exports_order, |a, b, module| { cmp_text_case_insensitive(a.text_fast(module), b.text_fast(module)) }) }; @@ -30,13 +34,15 @@ pub fn get_node_sorter_from_order<'a>(order: SortOrder) -> Option Some(Box::new(|(a_index, a), (b_index, b), program| { + SortOrder::CaseSensitive => Some(Box::new(move |(a_index, a), (b_index, b), program| { let result = if is_import_or_export_declaration(&a) { - cmp_optional_nodes(a, b, program, |a, b, module| { + cmp_optional_nodes(a, b, program, named_type_imports_exports_order, |a, b, module| { cmp_module_specifiers(a.text_fast(module), b.text_fast(module), cmp_text_case_sensitive) }) } else { - cmp_optional_nodes(a, b, program, |a, b, module| cmp_text_case_sensitive(a.text_fast(module), b.text_fast(module))) + cmp_optional_nodes(a, b, program, named_type_imports_exports_order, |a, b, module| { + cmp_text_case_sensitive(a.text_fast(module), b.text_fast(module)) + }) }; if result == Ordering::Equal { a_index.cmp(&b_index) @@ -51,11 +57,12 @@ fn cmp_optional_nodes<'a>( a: Option>, b: Option>, program: Program<'a>, + named_type_imports_exports_order: NamedTypeImportsExportsOrder, cmp_func: impl Fn(&SourceRange, &SourceRange, Program<'a>) -> Ordering, ) -> Ordering { if let Some(a) = a { if let Some(b) = b { - cmp_nodes(a, b, program, cmp_func) + cmp_nodes(a, b, program, named_type_imports_exports_order, cmp_func) } else { Ordering::Greater } @@ -66,15 +73,36 @@ fn cmp_optional_nodes<'a>( } } -fn cmp_nodes<'a>(a: Node<'a>, b: Node<'a>, program: Program<'a>, cmp_func: impl Fn(&SourceRange, &SourceRange, Program<'a>) -> Ordering) -> Ordering { +fn cmp_nodes<'a>( + a: Node<'a>, + b: Node<'a>, + program: Program<'a>, + named_type_imports_exports_order: NamedTypeImportsExportsOrder, + cmp_func: impl Fn(&SourceRange, &SourceRange, Program<'a>) -> Ordering, +) -> Ordering { let a_nodes = get_comparison_nodes(a); let b_nodes = get_comparison_nodes(b); for (i, a) in a_nodes.iter().enumerate() { if let Some(b) = b_nodes.get(i) { - let cmp_result = cmp_func(a, b, program); - if cmp_result != Ordering::Equal { - return cmp_result; + match (a, b) { + (ComparisonNode::HasType, ComparisonNode::NoType) => match named_type_imports_exports_order { + NamedTypeImportsExportsOrder::First => return Ordering::Less, + NamedTypeImportsExportsOrder::Last => return Ordering::Greater, + NamedTypeImportsExportsOrder::None => {} + }, + (ComparisonNode::NoType, ComparisonNode::HasType) => match named_type_imports_exports_order { + NamedTypeImportsExportsOrder::First => return Ordering::Greater, + NamedTypeImportsExportsOrder::Last => return Ordering::Less, + NamedTypeImportsExportsOrder::None => {} + }, + (ComparisonNode::Node(a), ComparisonNode::Node(b)) => { + let cmp_result = cmp_func(a, b, program); + if cmp_result != Ordering::Equal { + return cmp_result; + } + } + _ => {} } } else { return Ordering::Greater; @@ -88,42 +116,58 @@ fn cmp_nodes<'a>(a: Node<'a>, b: Node<'a>, program: Program<'a>, cmp_func: impl } } -fn get_comparison_nodes(node: Node) -> Vec { +enum ComparisonNode { + HasType, + NoType, + Node(SourceRange), +} + +fn get_comparison_nodes(node: Node) -> Vec { match node { Node::ImportNamedSpecifier(node) => { + let first_node = if node.is_type_only() { + ComparisonNode::HasType + } else { + ComparisonNode::NoType + }; if let Some(imported) = &node.imported { - vec![imported.range(), node.local.range()] + vec![first_node, ComparisonNode::Node(imported.range()), ComparisonNode::Node(node.local.range())] } else { - vec![node.local.range()] + vec![first_node, ComparisonNode::Node(node.local.range())] } } Node::ExportNamedSpecifier(node) => { + let first_node = if node.is_type_only() { + ComparisonNode::HasType + } else { + ComparisonNode::NoType + }; if let Some(exported) = &node.exported { - vec![node.orig.range(), exported.range()] + vec![first_node, ComparisonNode::Node(node.orig.range()), ComparisonNode::Node(exported.range())] } else { - vec![node.orig.range()] + vec![first_node, ComparisonNode::Node(node.orig.range())] } } Node::ImportDecl(node) => { - vec![node.src.range()] + vec![ComparisonNode::Node(node.src.range())] } Node::NamedExport(node) => { if let Some(src) = &node.src { - vec![src.range()] + vec![ComparisonNode::Node(src.range())] } else if cfg!(debug_assertions) { unimplemented!("Should not call this for named exports with src."); } else { - vec![node.range()] + vec![ComparisonNode::Node(node.range())] } } Node::ExportAll(node) => { - vec![node.src.range()] + vec![ComparisonNode::Node(node.src.range())] } _ => { if cfg!(debug_assertions) { unimplemented!("Not implemented sort node."); } else { - vec![node.range()] + vec![ComparisonNode::Node(node.range())] } } } diff --git a/tests/specs/declarations/export/NamedExports_SortNamedTypeExports_First.txt b/tests/specs/declarations/export/NamedExports_SortNamedTypeExports_First.txt new file mode 100644 index 00000000..4e5a1db0 --- /dev/null +++ b/tests/specs/declarations/export/NamedExports_SortNamedTypeExports_First.txt @@ -0,0 +1,18 @@ +~~ lineWidth: 40, exportDeclaration.sortTypeOnlyExports: first ~~ +== should sort first == +export { + type a, + testing, + other, + outttttttttttttttt, + type z, +} from "asdf"; + +[expect] +export { + type a, + type z, + other, + outttttttttttttttt, + testing, +} from "asdf"; diff --git a/tests/specs/declarations/export/NamedExports_SortNamedTypeExports_Last.txt b/tests/specs/declarations/export/NamedExports_SortNamedTypeExports_Last.txt new file mode 100644 index 00000000..b259c43a --- /dev/null +++ b/tests/specs/declarations/export/NamedExports_SortNamedTypeExports_Last.txt @@ -0,0 +1,18 @@ +~~ lineWidth: 40, exportDeclaration.sortTypeOnlyExports: last ~~ +== should sort == +export { + type a, + testing, + other, + outttttttttttttttt, + type z, +} from "asdf"; + +[expect] +export { + other, + outttttttttttttttt, + testing, + type a, + type z, +} from "asdf"; diff --git a/tests/specs/declarations/export/NamedExports_SortNamedTypeExports_None.txt b/tests/specs/declarations/export/NamedExports_SortNamedTypeExports_None.txt new file mode 100644 index 00000000..bffd4aac --- /dev/null +++ b/tests/specs/declarations/export/NamedExports_SortNamedTypeExports_None.txt @@ -0,0 +1,18 @@ +~~ lineWidth: 40, exportDeclaration.sortTypeOnlyExports: none ~~ +== should sort == +export { + type a, + testing, + other, + outttttttttttttttt, + type z, +} from "asdf"; + +[expect] +export { + type a, + other, + outttttttttttttttt, + testing, + type z, +} from "asdf"; diff --git a/tests/specs/declarations/export/NamedExports_SortOrder_CaseInsensitive.txt b/tests/specs/declarations/export/NamedExports_SortOrder_CaseInsensitive.txt index af4674af..a0c557a2 100644 --- a/tests/specs/declarations/export/NamedExports_SortOrder_CaseInsensitive.txt +++ b/tests/specs/declarations/export/NamedExports_SortOrder_CaseInsensitive.txt @@ -72,3 +72,21 @@ export { // test outttttttttttttttt, testing, } from "asdf"; + +== should sort type imports last by default == +export { + type a, + testing, + other, + outttttttttttttttt, + type z, +} from "asdf"; + +[expect] +export { + other, + outttttttttttttttt, + testing, + type a, + type z, +} from "asdf"; diff --git a/tests/specs/declarations/import/NamedImports_SortNamedTypeImports_First.txt b/tests/specs/declarations/import/NamedImports_SortNamedTypeImports_First.txt new file mode 100644 index 00000000..c8984b7c --- /dev/null +++ b/tests/specs/declarations/import/NamedImports_SortNamedTypeImports_First.txt @@ -0,0 +1,18 @@ +~~ lineWidth: 40, importDeclaration.sortTypeOnlyImports: first ~~ +== should sort first == +import { + type a, + testing, + other, + outttttttttttttttt, + type z, +} from "asdf"; + +[expect] +import { + type a, + type z, + other, + outttttttttttttttt, + testing, +} from "asdf"; diff --git a/tests/specs/declarations/import/NamedImports_SortNamedTypeImports_Last.txt b/tests/specs/declarations/import/NamedImports_SortNamedTypeImports_Last.txt new file mode 100644 index 00000000..8c5b50bb --- /dev/null +++ b/tests/specs/declarations/import/NamedImports_SortNamedTypeImports_Last.txt @@ -0,0 +1,18 @@ +~~ lineWidth: 40, importDeclaration.sortTypeOnlyImports: last ~~ +== should sort == +import { + type a, + testing, + other, + outttttttttttttttt, + type z, +} from "asdf"; + +[expect] +import { + other, + outttttttttttttttt, + testing, + type a, + type z, +} from "asdf"; diff --git a/tests/specs/declarations/import/NamedImports_SortNamedTypeImports_None.txt b/tests/specs/declarations/import/NamedImports_SortNamedTypeImports_None.txt new file mode 100644 index 00000000..ab3905f6 --- /dev/null +++ b/tests/specs/declarations/import/NamedImports_SortNamedTypeImports_None.txt @@ -0,0 +1,18 @@ +~~ lineWidth: 40, importDeclaration.sortTypeOnlyImports: none ~~ +== should sort == +import { + type a, + testing, + other, + outttttttttttttttt, + type z, +} from "asdf"; + +[expect] +import { + type a, + other, + outttttttttttttttt, + testing, + type z, +} from "asdf"; diff --git a/tests/specs/declarations/import/NamedImports_SortOrder_CaseInsensitive.txt b/tests/specs/declarations/import/NamedImports_SortOrder_CaseInsensitive.txt index 8d6ebdbc..6e28b6af 100644 --- a/tests/specs/declarations/import/NamedImports_SortOrder_CaseInsensitive.txt +++ b/tests/specs/declarations/import/NamedImports_SortOrder_CaseInsensitive.txt @@ -72,3 +72,21 @@ import { // test outttttttttttttttt, testing, } from "asdf"; + +== should sort with types last == +import { + type a, + testing, + other, + outttttttttttttttt, + type b, +} from "asdf"; + +[expect] +import { + other, + outttttttttttttttt, + testing, + type a, + type b, +} from "asdf";