Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ability to sort type only named imports and exports first or last #664

Merged
merged 1 commit into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions deployment/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
18 changes: 17 additions & 1 deletion src/configuration/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,13 +536,27 @@ 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
pub fn export_declaration_sort_named_exports(&mut self, value: SortOrder) -> &mut Self {
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`).
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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);
}
Expand Down
12 changes: 12 additions & 0 deletions src/configuration/resolve_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
15 changes: 15 additions & 0 deletions src/configuration/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
14 changes: 10 additions & 4 deletions src/generation/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1544,8 +1544,14 @@ fn gen_named_import_or_export_specifiers<'a>(opts: GenNamedImportOrExportSpecifi
context: &Context<'a>,
) -> Option<Box<dyn Fn((usize, Option<Node<'a>>), (usize, Option<Node<'a>>), 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!(),
}
}
Expand Down Expand Up @@ -7104,8 +7110,8 @@ fn gen_statements<'a>(inner_range: SourceRange, stmts: Vec<Node<'a>>, context: &
context: &Context<'a>,
) -> Option<Box<dyn Fn((usize, Option<Node<'a>>), (usize, Option<Node<'a>>), 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,
}
}
Expand Down
90 changes: 67 additions & 23 deletions src/generation/sorting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Box<dyn Fn((usize, Option<Node<'a>>), (usize, Option<Node<'a>>), Program<'a>) -> Ordering>> {
pub fn get_node_sorter_from_order<'a>(
order: SortOrder,
named_type_imports_exports_order: NamedTypeImportsExportsOrder,
) -> Option<Box<dyn Fn((usize, Option<Node<'a>>), (usize, Option<Node<'a>>), 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))
})
};
Expand All @@ -30,13 +34,15 @@ pub fn get_node_sorter_from_order<'a>(order: SortOrder) -> Option<Box<dyn Fn((us
result
}
})),
SortOrder::CaseSensitive => 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)
Expand All @@ -51,11 +57,12 @@ fn cmp_optional_nodes<'a>(
a: Option<Node<'a>>,
b: Option<Node<'a>>,
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
}
Expand All @@ -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;
Expand All @@ -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<SourceRange> {
enum ComparisonNode {
HasType,
NoType,
Node(SourceRange),
}

fn get_comparison_nodes(node: Node) -> Vec<ComparisonNode> {
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())]
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Original file line number Diff line number Diff line change
@@ -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";
Original file line number Diff line number Diff line change
@@ -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";
Loading
Loading